spcm

A high-level, general purpose, object-oriented Python package to control Spectrum Instrumentation GmbH devices.

spcm can connect to digitizers, AWGs, StarHubs and Netboxes. The package contains classes for controlling specific cards and synchronization devices (StarHub) as well as for specific functionality, such as DDS and TimeStamps.

Classes

Hardware classes

Hardware classes are the interfaces to the actual devices. These hardware classes support context management, hence the opening of specific devices is handled as a file open, using the Python with-statement.

Name Parent Description
Device (none) general base class for connecting to Spectrum Instrumentation GmbH devices and an interface for other classes.
Card Device a class to control the low-level API interface of Spectrum Instrumentation cards.
Sync Device a class for controling StarHub devices.
CardStack ExitStack a class that handles the opening and closing of a combination of different cards either with or without a StarHub that synchronizes the cards.
Netbox CardStack a class that handles the opening and closing of a group of cards combined in a Netbox.

Diagram

classDiagram class Device class Card class Sync class `contextlib.ExitStack` class CardStack class Netbox Device <|-- Card Device <|-- Sync `contextlib.ExitStack` <|-- CardStack CardStack <|-- Netbox

Functionality classes

Functionality classes handle specific functionality that is available to the card, as well as specific add-on options. A functionality class is provided with a hardware class object to handle functionality on that card. Some classes, in addition, also support groups of cards and can be provided with objects of type CardStack.

Name Parent Description
CardFunctionality (none) interface class for additional card functionality
Channels (none) class for controlling the channels of a card or a stack of cards
Channel (none) class for controlling a single channel
Clock CardFunctionality class for setting up the clock engine of the card
Trigger CardFunctionality class for setting up the trigger engine of the card
MultiPurposeIOs CardFunctionality class for setting up the multi purpose i/o's of the card
MultiPurposeIO (none) class for handling a single multi purpose i/o line a list of these objects resides inside MultiPurposeIOs
SynchronousDigitalIOs MultiPurposeIOs class for handling synchronous digital I/O on the xio lines
DataTransfer CardFunctionality special class for handling data transfer functionality
Multi DataTransfer special class for handling multiple recording and replay mode functionality
Gated DataTransfer special class for handling gated recording and replay functionality
Sequence DataTransfer special class for handling sequence mode functionality
TimeStamp DataTransfer special class for handling time stamped data
SCAPPTransfer DataTransfer special class for handling direct card to GPU class using the SCAPP option
Boxcar Multi special class for handling boxcar averaging
BlockAverage Multi special class for handling block averaging functionality
BlockStatistics Multi special class for handling block statistics functionality
PulseGenerators CardFunctionality class for handling the pulse generator functionality
PulseGenerator (none) class for handling a single pulse generator a list of these objects resides inside PulseGenerators
DDS CardFunctionality class for handling DDS functionality
DDSCore (none) class for handling a DDS core, a list of these objects resides inside a DDS object
DDSCommandList DDS class for handling streaming DDS commands in blocks
DDSCommandQueue DDS class for handling streaming DDS commands in queues, where commands are added to the queue and automatically written to the card

Diagram

classDiagram class CardFunctionality <<interface>> CardFunctionality class Channels class Clock class Trigger class MultiPurposeIOs class SynchronousIOs class DataTransfer class DDS class DDSCore class DDSCommandList class DDSCommandQueue class PulseGenerators class Multi class Gated class TimeStamp class Sequence class SCAPPTransfer class Boxcar class BlockAverage class BlockStatistics CardFunctionality <|-- Clock CardFunctionality <|-- Trigger CardFunctionality <|-- MultiPurposeIOs CardFunctionality <|-- DataTransfer CardFunctionality <|-- DDS CardFunctionality <|-- PulseGenerators DataTransfer <|-- Multi DataTransfer <|-- Gated DataTransfer <|-- TimeStamp DataTransfer <|-- Sequence DataTransfer <|-- SCAPPTransfer Multi <|-- Boxcar Multi <|-- BlockAverage Multi <|-- BlockStatistics Channels *-- Channel MultiPurposeIOs *-- MultiPurposeIO MultiPurposeIOs <|-- SynchronousIOs PulseGenerators *-- PulseGenerator DDS *-- DDSCore DDS <|-- DDSCommandList DDSCommandList <|-- DDSCommandQueue

Exception classes

When an error in the driver occures, the user is notified with an exception that contains an error object. Timeouts are also handled through exceptions and have their own class.

Name Parent Description
SpcmException (none) the main class to control exceptions that are raised due to errors that are raised by the low-level driver.
SpcmTimeout SpcmException when an timeout of the device occurs a special exception is raised of type SpcmTimeout
SpcmDeviceNotFound SpcmException when a device is not found this is not necessarily an error, hence handling this is special

Diagram

classDiagram class SpcmTimeout class SpcmException SpcmException <|-- SpcmTimeout SpcmException <|-- SpcmDeviceNotFound

Error classes

Errors coming from the driver API, are stored in an error object and then raised as an exception. The error object contains all the information coming from the driver.

Name Parent Description
SpcmError (none) all the errors that are raised by the low-level driver are packed into objects of this class and handled through exceptions

Diagram

classDiagram class SpcmError

Notes

  • See the package spcm-core for an extensive list of all the register names and errors that are handled by the driver.
  • For more information, please have a look at our hardware specific user manuals.

See also

Supported devices

The following product series are supported:

 1"""
 2.. include:: ./README.md
 3
 4.. include:: ./SUPPORTED_DEVICES.md
 5"""
 6
 7__docformat__ = "numpy"
 8import numpy as np
 9
10# Units available
11from pint import UnitRegistry
12try:
13    import matplotlib.pyplot as plt
14    mpl = True
15except ImportError:
16    mpl = False
17units = UnitRegistry(autoconvert_offset_to_baseunit=True)
18units.define("sample = 1 = Sa = Sample = Samples = S")
19units.define("promille = 0.001 = ‰ = permille = perthousand = perthousands = ppt")
20units.define("fraction = 1 = frac = Frac = Fracs = Fraction = Fractions = Frac = Fracs")
21units.highZ = np.inf * units.ohm
22units.formatter.default_format = "~P" # see https://pint.readthedocs.io/en/stable/user/formatting.html
23if mpl:
24    units.setup_matplotlib(mpl)
25    units.mpl_formatter = "{:~P}" # see https://pint.readthedocs.io/en/stable/user/plotting.html
26__all__ = ["units"]
27
28# Import all registery entries and spectrum card errors into the module's name space
29from spcm_core import *
30
31# Import all the public classes into the module's namespace
32from .classes_device import Device
33from .classes_card import Card
34from .classes_error_exception import SpcmError, SpcmException, SpcmTimeout, SpcmDeviceNotFound
35from .classes_sync import Sync
36from .classes_card_stack import CardStack
37from .classes_netbox import Netbox
38# Functionality
39from .classes_functionality import CardFunctionality
40from .classes_channels import Channels, Channel
41from .classes_clock import Clock
42from .classes_trigger import Trigger
43from .classes_multi_purpose_ios import MultiPurposeIO, MultiPurposeIOs
44from .classes_data_transfer import DataTransfer
45from .classes_gated import Gated
46from .classes_multi import Multi
47from .classes_time_stamp import TimeStamp
48from .classes_sequence import Sequence
49from .classes_dds import DDS, DDSCore
50from .classes_dds_command_list import DDSCommandList
51from .classes_dds_command_queue import DDSCommandQueue
52from .classes_pulse_generators import PulseGenerator, PulseGenerators
53from .classes_aba import ABA
54from .classes_block_average import BlockAverage
55from .classes_block_statistics import BlockStatistics
56from .classes_boxcar import Boxcar
57from .classes_scapp import SCAPPTransfer
58from .classes_synchronous_digital_ios import SynchronousDigitalIOs
59
60__all__ = [*__all__,
61    "Device", "Card", "Sync", "CardStack", "Netbox", "CardFunctionality", "Channels", "Channel", "Clock", "Trigger", "MultiPurposeIOs", "MultiPurposeIO",
62    "DataTransfer", "DDS", "DDSCore", "DDSCommandList", "DDSCommandQueue", "PulseGenerator", "PulseGenerators", "Multi", "Gated", "TimeStamp", "Sequence", "ABA",
63    "BlockAverage", "Boxcar", "BlockStatistics", "SpcmException", "SpcmTimeout", "SpcmDeviceNotFound", "SpcmError", "SCAPPTransfer", "SynchronousDigitalIOs"
64]
65
66# Versioning support using versioneer
67from . import _version
68__version__ = _version.get_versions()['version']
69
70# Writing spcm package version to log file
71try:
72    driver_version = int64(0)
73    spcm_dwGetParam_i64(None, SPC_GETDRVVERSION, byref(driver_version))
74    version_hex = driver_version.value
75    major = (version_hex & 0xFF000000) >> 24
76    minor = (version_hex & 0x00FF0000) >> 16
77    build = version_hex & 0x0000FFFF
78    # Available starting from build 21797
79    if build < 21797:
80        version_str = "v{}.{}.{}".format(major, minor, build)
81        raise OSError(f"Driver version build {version_str} does not support writing spcm version to log")
82    from importlib.metadata import version
83    version_tag = version('spcm')
84    version_str = bytes("Python package spcm v{}".format(version_tag), "utf-8")
85    version_ptr = create_string_buffer(version_str)
86    dwErr = spcm_dwSetParam_ptr(None, SPC_WRITE_TO_LOG, version_ptr, len(version_str))
87except OSError as e:
88    print(e)
units = <pint.registry.UnitRegistry object>
class Device:
 21class Device():
 22    """
 23    a class to control the low-level API interface of Spectrum Instrumentation devices
 24
 25    For more information about what setups are available, please have a look at the user manual
 26    for your specific device.
 27
 28    Parameters
 29    ----------
 30    device_identifier
 31        the identifying string that defines the used device
 32
 33    Raises
 34    ----------
 35    SpcmException
 36    SpcmTimeout
 37    """
 38
 39    # public
 40    device_identifier : str = ""
 41
 42    # private
 43    _kwargs : Dict[str, Union[int, float, str]] = {}
 44    _last_error = None
 45    _handle = None
 46    """the handle object used for the card connection"""
 47
 48    _str_len = 256
 49    _reraise : bool = False
 50    _throw_error : bool = True
 51    _verbose : bool = False
 52    _closed : bool = True
 53    """the indicator that indicated whether a connection is opened or closed is set to open (False)"""
 54
 55
 56    def __init__(self, device_identifier : str = "", handle = False, **kwargs) -> None:
 57        """Puts the device_identifier in the class parameter self.device_parameter
 58
 59        Parameters
 60        ----------
 61        device_identifier : str = ""
 62            an identifier string to connect to a specific device, for example:
 63
 64            * Local PCIe device '/dev/spcm0'
 65            * Remote 'TCPIP::192.168.1.10::inst0::INSTR'
 66        handle = False
 67            directly supply the object with an existing handle
 68        """
 69        self.device_identifier = device_identifier
 70        self._handle = handle
 71        self._kwargs = kwargs
 72        self._throw_error = kwargs.get("throw_error", True)
 73        self._verbose = kwargs.get("verbose", False)
 74    
 75    def __del__(self) -> None:
 76        """Destructor that closes the connection associated with the handle"""
 77        self.close()
 78
 79    def __enter__(self) -> object:
 80        """
 81        Constructs a handle using the parameter `device_identifier`, when using the with statement
 82        
 83        Returns
 84        -------
 85        object
 86            The active card handle
 87        
 88        Raises
 89        ------
 90        SpcmException
 91        """
 92        return self.open()
 93
 94    def open(self, device_identifier : str = None) -> object:
 95        """
 96        Opens a connection to the card and creates a handle, when no with statement is used
 97
 98        Parameters
 99        ----------
100        device_identifier : str
101            The card identifier string (e.g. '/dev/spcm0' for a local device
102            NOTE: this is to keep the API consistent with a previous version. The original open()
103            method is now in _open()
104
105        Returns
106        -------
107        object
108            This Card object
109        """
110            
111        if device_identifier: # this is to keep the API consistent
112            return self.open_handle(device_identifier)
113        # This used to be in enter. It is now split up to allow for the open method
114        # to be used when no with statement is used
115        if self.device_identifier and not self._handle:
116            self.open_handle(self.device_identifier)
117            if not self._handle and self._throw_error:
118                error = SpcmError(text="{} not found...".format(self.device_identifier))
119                raise SpcmDeviceNotFound(error)
120            # if self._handle:
121            #     self._closed = False
122        return self
123    
124    def __exit__(self, exception : SpcmException = None, error_value : str = None, trace : types.TracebackType = None) -> None:
125        """
126        Handles the exiting of the with statement, when either no code is left or an exception is thrown before
127
128        Parameters
129        ----------
130        exception : SpcmException
131            Only this parameter is used and printed 
132        error_value : str
133        trace : types.TracebackType
134
135        Raises
136        ------
137        SpcmException
138        """
139        if self._verbose and exception:
140            self._print("Error type: {}".format(exception))
141            self._print("Error value: {}".format(error_value))
142            self._print("Traceback:")
143            traceback.print_tb(trace)
144        elif exception:
145            self._print("Error: {}".format(error_value))
146        # self.stop(M2CMD_DATA_STOPDMA) # stop the card and the DMA transfer
147        self.close()
148        if exception and self._reraise:
149            raise exception
150        
151    def close(self) -> None:
152        """
153        Closes the connection to the card using a handle
154        """
155
156        if not self._closed:
157            self.stop(M2CMD_DATA_STOPDMA) # stop the card and the DMA transfer
158            self.close_handle()
159    
160    def handle(self) -> object:
161        """
162        Returns the handle used by the object to connect to the active card
163    
164        Class Parameters
165        ----------
166        self._handle
167    
168        Returns
169        -------
170        drv_handle
171            The active card handle
172        """
173        
174        return self._handle
175
176    # Check if a card was found
177    def __bool__(self) -> bool:
178        """
179        Check for a connection to the active card
180    
181        Class Parameters
182        ----------
183        self._handle
184    
185        Returns
186        -------
187        bool
188            True for an active connection and false otherwise
189            
190        Examples
191        -----------
192        >>> card = spcm.Card('/dev/spcm0')
193        >>> print(bool(card))
194        <<< True # if a card was found at '/dev/spcm0'
195        """
196        
197        return bool(self._handle)
198    
199    # High-level parameter functions, that use the low-level get and set function    
200    def drv_type(self) -> int:
201        """
202        Get the driver type of the currently used driver (see register `SPC_GETDRVTYPE` in the manual)
203    
204        Returns
205        -------
206        int
207            The driver type of the currently used driver
208        """
209
210        return self.get_i(SPC_GETDRVTYPE)
211
212    def drv_version(self) -> dict:
213        """
214        Get the version of the currently used driver. (see register `SPC_GETDRVVERSION` in the manual)
215    
216        Returns
217        -------
218        dict
219            version of the currently used driver
220              * "major" - the major version number,
221              * "minor" - the minor version number,
222              * "build" - the actual build
223        """
224        version_hex = self.get_i(SPC_GETDRVVERSION)
225        major = (version_hex & 0xFF000000) >> 24
226        minor = (version_hex & 0x00FF0000) >> 16
227        build = version_hex & 0x0000FFFF
228        version_dict = {"major": major, "minor": minor, "build": build}
229        return version_dict
230    
231    def kernel_version(self) -> dict:
232        """
233        Get the version of the currently used kernel. (see register `SPC_GETKERNELVERSION` in the manual)
234    
235        Returns
236        -------
237        dict 
238            version of the currently used driver
239              * "major" - the major version number,
240              * "minor" - the minor version number,
241              * "build" - the actual build
242        """
243        version_hex = self.get_i(SPC_GETKERNELVERSION)
244        major = (version_hex & 0xFF000000) >> 24
245        minor = (version_hex & 0x00FF0000) >> 16
246        build = version_hex & 0x0000FFFF
247        version_dict = {"major": major, "minor": minor, "build": build}
248        return version_dict
249    
250    def custom_modifications(self) -> dict:
251        """
252        Get the custom modifications of the currently used device. (see register `SPCM_CUSTOMMOD` in the manual)
253    
254        Returns
255        -------
256        dict
257            The custom modifications of the currently used device
258              * "starhub" - custom modifications to the starhub,
259              * "module" -  custom modification of the front-end module(s)
260              * "base" - custom modification of the base card
261        """
262
263        custom_mode = self.get_i(SPCM_CUSTOMMOD)
264        starhub = (custom_mode & SPCM_CUSTOMMOD_STARHUB_MASK) >> 16
265        module = (custom_mode & SPCM_CUSTOMMOD_MODULE_MASK) >> 8
266        base = custom_mode & SPCM_CUSTOMMOD_BASE_MASK
267        custom_dict = {"starhub": starhub, "module": module, "base": base}
268        return custom_dict
269    
270    def log_level(self, log_level : int = None) -> int:
271        """
272        Set the logging level of the driver
273    
274        Parameters
275        ----------
276        log_level : int
277            The logging level that is set for the driver
278        
279        Returns
280        -------
281        int
282            The logging level of the driver
283        """
284
285        if log_level is not None:
286            self.set_i(SPC_LOGDLLCALLS, log_level)
287        return self.get_i(SPC_LOGDLLCALLS)
288
289    def cmd(self, *args) -> None:
290        """
291        Execute spcm commands (see register `SPC_M2CMD` in the manual)
292    
293        Parameters
294        ----------
295        *args : int
296            The different command flags to be executed.
297        """
298
299        cmd = 0
300        for arg in args:
301            cmd |= arg
302        self.set_i(SPC_M2CMD, cmd)
303    
304    #@Decorators.unitize(units.ms, "timeout", int)
305    def timeout(self, timeout : int = None, return_unit = None) -> int:
306        """
307        Sets the timeout in ms (see register `SPC_TIMEOUT` in the manual)
308        
309        Parameters
310        ----------
311        timeout : int
312            The timeout in ms
313        
314        Returns
315        -------
316        int
317            returns the current timeout in ms
318        """
319
320        if timeout is not None:
321            timeout = UnitConversion.convert(timeout, units.ms, int)
322            self.set_i(SPC_TIMEOUT, timeout)
323        return_value = self.get_i(SPC_TIMEOUT)
324        return_value = UnitConversion.to_unit(return_value * units.ms, return_unit)
325        return return_value
326    
327    def start(self, *args) -> None:
328        """
329        Starts the connected card and enables triggering on the card (see command `M2CMD_CARD_START` in the manual)
330
331        Parameters
332        ----------
333        *args : int
334            flags that are send together with the start command
335        """
336
337        self.cmd(M2CMD_CARD_START, *args)
338    
339    def stop(self, *args : int) -> None:
340        """
341        Stops the connected card (see command `M2CMD_CARD_STOP` in the manual)
342
343        Parameters
344        ----------
345        *args : int
346            flags that are send together with the stop command (e.g. M2CMD_DATA_STOPDMA)
347        """
348
349        self.cmd(M2CMD_CARD_STOP, *args)
350    
351    def reset(self) -> None:
352        """
353        Resets the connected device (see command `M2CMD_CARD_RESET` in the manual)
354        """
355
356        self.cmd(M2CMD_CARD_RESET)
357
358    def write_setup(self, *args) -> None:
359        """
360        Writes of the configuration registers previously changed to the device (see command `M2CMD_CARD_WRITESETUP` in the manual)
361
362        Parameters
363        ----------
364        *args : int
365            flags that are set with the write command
366        """
367
368        self.cmd(M2CMD_CARD_WRITESETUP, *args)
369    
370    def register_list(self, register_list : List[dict[str, Union[int, float]]]) -> None:
371        """
372        Writes a list with dictionaries, where each dictionary corresponds to a command (see the user manual of your device for all the available registers)
373    
374        Parameters
375        ----------
376        register_list : List[dict[str, Union[int, float]]]
377            The list of commands that needs to written to the specific registers of the card.
378        """
379
380        c_astParams = (ST_LIST_PARAM * 1024)()
381        astParams = ctypes.cast(c_astParams, ctypes.POINTER(ST_LIST_PARAM))
382        for i, register in enumerate(register_list):
383            astParams[i].lReg = register["lReg"]
384            astParams[i].lType = register["lType"]
385            if register["lType"] == TYPE_INT64:
386                astParams[i].Value.llValue = register["llValue"]
387            elif register["lType"] == TYPE_DOUBLE:
388                astParams[i].Value.dValue = register["dValue"]
389        self.set_ptr(SPC_REGISTER_LIST, astParams, len(register_list) * ctypes.sizeof(ST_LIST_PARAM))
390
391    # Low-level get and set functions
392    def get_i(self, register : int) -> int:
393        """
394        Get the integer value of a specific register of the card (see the user manual of your device for all the available registers)
395    
396        Parameters
397        ----------
398        register : int
399            The specific register that will be read from.
400        
401        Returns
402        -------
403        int
404            The value as stored in the specific register
405        """
406
407        self._check_closed()
408        return_value = int64(0)
409        dwErr = spcm_dwGetParam_i64(self._handle, register, byref(return_value))
410        self._check_error(dwErr)
411        return return_value.value
412    get = get_i
413    """Alias of get_i"""
414    
415    def get_d(self, register : int) -> float:
416        """
417        Get the float value of a specific register of the card (see the user manual of your device for all the available registers)
418    
419        Parameters
420        ----------
421        register : int
422            The specific register that will be read from.
423        
424        Returns
425        -------
426        float
427            The value as stored in the specific register
428        """
429
430        self._check_closed()
431        return_value = c_double(0)
432        self._check_error(spcm_dwGetParam_d64(self._handle, register, byref(return_value)))
433        return return_value.value
434    
435    def get_str(self, register : int) -> str:
436        """
437        Get the string value of a specific register of the card (see the user manual of your device for all the available registers)
438    
439        Parameters
440        ----------
441        register : int
442            The specific register that will be read from.
443        
444        Returns
445        -------
446        str
447            The value as stored in the specific register
448        """
449
450        self._check_closed()
451        return_value = create_string_buffer(self._str_len)
452        self._check_error(spcm_dwGetParam_ptr(self._handle, register, byref(return_value), self._str_len))
453        return return_value.value.decode('utf-8')
454    
455    def set_i(self, register : int, value : int) -> None:
456        """
457        Write the value of a specific register to the card (see the user manual of your device for all the available registers)
458    
459        Parameters
460        ----------
461        register : int
462            The specific register that will be written.
463        value : int
464            The value that is written to the card.
465        """
466
467        self._check_closed()
468        self._check_error(spcm_dwSetParam_i64(self._handle, register, value))
469    
470    def set_d(self, register : int, value : float) -> None:
471        """
472        Write the value of a specific register to the card (see the user manual of your device for all the available registers)
473    
474        Parameters
475        ----------
476        register : int
477            The specific register that will be written.
478        value : float
479            The value that is written to the card.
480        """
481
482        self._check_closed()
483        self._check_error(spcm_dwSetParam_d64(self._handle, register, value))
484    
485    def set_ptr(self, register : int, reference : c_void_p, size : int) -> None:
486        """
487        Use a memory segment to write to a specific register of the card (see the user manual of your device for all the available registers)
488    
489        Parameters
490        ----------
491        register : int
492            The specific register that will be read from.
493        reference : c_void_p
494            pointer to the memory segment
495        size : int
496            size of the memory segment
497        
498        Returns
499        -------
500        int
501            The value as stored in the specific register
502        """
503
504        self._check_closed()
505        self._check_error(spcm_dwSetParam_ptr(self._handle, register, reference, size))
506
507    # Error handling and exception raising
508    def _check_error(self, dwErr : int):
509        """
510        Create an SpcmError object and check for the last error (see the appendix in the user manual of your device for all the possible error codes)
511    
512        Parameters
513        ----------
514        dwErr : int
515            The error value as returned from a direct driver call
516        
517        Raises
518        ------
519        SpcmException
520        SpcmTimeout
521        """
522
523        # pass
524        if dwErr not in [ERR_OK, ERR_TIMEOUT] and self._throw_error:
525            self.get_error_info()
526            raise SpcmException(self._last_error)
527        elif dwErr == ERR_TIMEOUT:
528            raise SpcmTimeout("A card timeout occured")
529
530    def get_error_info(self) -> SpcmError:
531        """
532        Create an SpcmError object and store it in an object parameter
533    
534        Returns
535        ----------
536        SpcmError
537            the Error object containing the last error
538        """
539
540        self._last_error = SpcmError(self._handle)
541        return self._last_error
542    
543    def _check_closed(self) -> None:
544        """
545        Check if a connection to the card exists and if not throw an error
546
547        Raises
548        ------
549        SpcmException
550        """
551        if self._closed:
552            error_text = "The connection to the card has been closed. Please reopen the connection before sending commands."
553            if self._throw_error:
554                raise SpcmException(text=error_text)
555            else:
556                self._print(error_text)
557    
558    def _print(self, text : str, verbose : bool = False, **kwargs) -> None:
559        """
560        Print information
561
562        Parameters
563        ----------
564        text : str
565            The text that is printed
566        verbose : bool
567            A boolean that indicates if the text should forced to be printed
568        **kwargs
569            Additional parameters that are passed to the print function
570    
571        """
572
573        if self._verbose or verbose:
574            print(text, **kwargs)
575
576    def open_handle(self, device_identifier : str) -> None:
577        """
578        Open a connection to the card and create a handle (see the user manual of your specific device on how to find out the device_identifier string)
579    
580        Parameters
581        ----------
582        device_identifier : str
583            The card identifier string (e.g. '/dev/spcm0' for a local device or 'TCPIP::192.168.1.10::inst0::INSTR' for a remote device)
584        """
585
586        self._handle = spcm_hOpen(create_string_buffer(bytes(device_identifier, 'utf-8')))
587        self._closed = False
588    
589    def close_handle(self) -> None:
590        """
591        Close a connection to the card using the handle
592        """
593
594        spcm_vClose(self._handle)
595        self._handle = None
596        self._closed = True

a class to control the low-level API interface of Spectrum Instrumentation devices

For more information about what setups are available, please have a look at the user manual for your specific device.

Parameters
  • device_identifier: the identifying string that defines the used device
Raises
  • SpcmException
  • SpcmTimeout
Device(device_identifier: str = '', handle=False, **kwargs)
56    def __init__(self, device_identifier : str = "", handle = False, **kwargs) -> None:
57        """Puts the device_identifier in the class parameter self.device_parameter
58
59        Parameters
60        ----------
61        device_identifier : str = ""
62            an identifier string to connect to a specific device, for example:
63
64            * Local PCIe device '/dev/spcm0'
65            * Remote 'TCPIP::192.168.1.10::inst0::INSTR'
66        handle = False
67            directly supply the object with an existing handle
68        """
69        self.device_identifier = device_identifier
70        self._handle = handle
71        self._kwargs = kwargs
72        self._throw_error = kwargs.get("throw_error", True)
73        self._verbose = kwargs.get("verbose", False)

Puts the device_identifier in the class parameter self.device_parameter

Parameters
  • device_identifier (str = ""): an identifier string to connect to a specific device, for example:

    • Local PCIe device '/dev/spcm0'
    • Remote 'TCPIP::192.168.1.10::inst0::INSTR'
  • handle = False: directly supply the object with an existing handle
device_identifier: str = ''
def open(self, device_identifier: str = None) -> object:
 94    def open(self, device_identifier : str = None) -> object:
 95        """
 96        Opens a connection to the card and creates a handle, when no with statement is used
 97
 98        Parameters
 99        ----------
100        device_identifier : str
101            The card identifier string (e.g. '/dev/spcm0' for a local device
102            NOTE: this is to keep the API consistent with a previous version. The original open()
103            method is now in _open()
104
105        Returns
106        -------
107        object
108            This Card object
109        """
110            
111        if device_identifier: # this is to keep the API consistent
112            return self.open_handle(device_identifier)
113        # This used to be in enter. It is now split up to allow for the open method
114        # to be used when no with statement is used
115        if self.device_identifier and not self._handle:
116            self.open_handle(self.device_identifier)
117            if not self._handle and self._throw_error:
118                error = SpcmError(text="{} not found...".format(self.device_identifier))
119                raise SpcmDeviceNotFound(error)
120            # if self._handle:
121            #     self._closed = False
122        return self

Opens a connection to the card and creates a handle, when no with statement is used

Parameters
  • device_identifier (str): The card identifier string (e.g. '/dev/spcm0' for a local device NOTE: this is to keep the API consistent with a previous version. The original open() method is now in _open()
Returns
  • object: This Card object
def close(self) -> None:
151    def close(self) -> None:
152        """
153        Closes the connection to the card using a handle
154        """
155
156        if not self._closed:
157            self.stop(M2CMD_DATA_STOPDMA) # stop the card and the DMA transfer
158            self.close_handle()

Closes the connection to the card using a handle

def handle(self) -> object:
160    def handle(self) -> object:
161        """
162        Returns the handle used by the object to connect to the active card
163    
164        Class Parameters
165        ----------
166        self._handle
167    
168        Returns
169        -------
170        drv_handle
171            The active card handle
172        """
173        
174        return self._handle

Returns the handle used by the object to connect to the active card

Class Parameters

self._handle

Returns
  • drv_handle: The active card handle
def drv_type(self) -> int:
200    def drv_type(self) -> int:
201        """
202        Get the driver type of the currently used driver (see register `SPC_GETDRVTYPE` in the manual)
203    
204        Returns
205        -------
206        int
207            The driver type of the currently used driver
208        """
209
210        return self.get_i(SPC_GETDRVTYPE)

Get the driver type of the currently used driver (see register SPC_GETDRVTYPE in the manual)

Returns
  • int: The driver type of the currently used driver
def drv_version(self) -> dict:
212    def drv_version(self) -> dict:
213        """
214        Get the version of the currently used driver. (see register `SPC_GETDRVVERSION` in the manual)
215    
216        Returns
217        -------
218        dict
219            version of the currently used driver
220              * "major" - the major version number,
221              * "minor" - the minor version number,
222              * "build" - the actual build
223        """
224        version_hex = self.get_i(SPC_GETDRVVERSION)
225        major = (version_hex & 0xFF000000) >> 24
226        minor = (version_hex & 0x00FF0000) >> 16
227        build = version_hex & 0x0000FFFF
228        version_dict = {"major": major, "minor": minor, "build": build}
229        return version_dict

Get the version of the currently used driver. (see register SPC_GETDRVVERSION in the manual)

Returns
  • dict: version of the currently used driver
    • "major" - the major version number,
    • "minor" - the minor version number,
    • "build" - the actual build
def kernel_version(self) -> dict:
231    def kernel_version(self) -> dict:
232        """
233        Get the version of the currently used kernel. (see register `SPC_GETKERNELVERSION` in the manual)
234    
235        Returns
236        -------
237        dict 
238            version of the currently used driver
239              * "major" - the major version number,
240              * "minor" - the minor version number,
241              * "build" - the actual build
242        """
243        version_hex = self.get_i(SPC_GETKERNELVERSION)
244        major = (version_hex & 0xFF000000) >> 24
245        minor = (version_hex & 0x00FF0000) >> 16
246        build = version_hex & 0x0000FFFF
247        version_dict = {"major": major, "minor": minor, "build": build}
248        return version_dict

Get the version of the currently used kernel. (see register SPC_GETKERNELVERSION in the manual)

Returns
  • dict: version of the currently used driver
    • "major" - the major version number,
    • "minor" - the minor version number,
    • "build" - the actual build
def custom_modifications(self) -> dict:
250    def custom_modifications(self) -> dict:
251        """
252        Get the custom modifications of the currently used device. (see register `SPCM_CUSTOMMOD` in the manual)
253    
254        Returns
255        -------
256        dict
257            The custom modifications of the currently used device
258              * "starhub" - custom modifications to the starhub,
259              * "module" -  custom modification of the front-end module(s)
260              * "base" - custom modification of the base card
261        """
262
263        custom_mode = self.get_i(SPCM_CUSTOMMOD)
264        starhub = (custom_mode & SPCM_CUSTOMMOD_STARHUB_MASK) >> 16
265        module = (custom_mode & SPCM_CUSTOMMOD_MODULE_MASK) >> 8
266        base = custom_mode & SPCM_CUSTOMMOD_BASE_MASK
267        custom_dict = {"starhub": starhub, "module": module, "base": base}
268        return custom_dict

Get the custom modifications of the currently used device. (see register SPCM_CUSTOMMOD in the manual)

Returns
  • dict: The custom modifications of the currently used device
    • "starhub" - custom modifications to the starhub,
    • "module" - custom modification of the front-end module(s)
    • "base" - custom modification of the base card
def log_level(self, log_level: int = None) -> int:
270    def log_level(self, log_level : int = None) -> int:
271        """
272        Set the logging level of the driver
273    
274        Parameters
275        ----------
276        log_level : int
277            The logging level that is set for the driver
278        
279        Returns
280        -------
281        int
282            The logging level of the driver
283        """
284
285        if log_level is not None:
286            self.set_i(SPC_LOGDLLCALLS, log_level)
287        return self.get_i(SPC_LOGDLLCALLS)

Set the logging level of the driver

Parameters
  • log_level (int): The logging level that is set for the driver
Returns
  • int: The logging level of the driver
def cmd(self, *args) -> None:
289    def cmd(self, *args) -> None:
290        """
291        Execute spcm commands (see register `SPC_M2CMD` in the manual)
292    
293        Parameters
294        ----------
295        *args : int
296            The different command flags to be executed.
297        """
298
299        cmd = 0
300        for arg in args:
301            cmd |= arg
302        self.set_i(SPC_M2CMD, cmd)

Execute spcm commands (see register SPC_M2CMD in the manual)

Parameters
  • *args (int): The different command flags to be executed.
def timeout(self, timeout: int = None, return_unit=None) -> int:
305    def timeout(self, timeout : int = None, return_unit = None) -> int:
306        """
307        Sets the timeout in ms (see register `SPC_TIMEOUT` in the manual)
308        
309        Parameters
310        ----------
311        timeout : int
312            The timeout in ms
313        
314        Returns
315        -------
316        int
317            returns the current timeout in ms
318        """
319
320        if timeout is not None:
321            timeout = UnitConversion.convert(timeout, units.ms, int)
322            self.set_i(SPC_TIMEOUT, timeout)
323        return_value = self.get_i(SPC_TIMEOUT)
324        return_value = UnitConversion.to_unit(return_value * units.ms, return_unit)
325        return return_value

Sets the timeout in ms (see register SPC_TIMEOUT in the manual)

Parameters
  • timeout (int): The timeout in ms
Returns
  • int: returns the current timeout in ms
def start(self, *args) -> None:
327    def start(self, *args) -> None:
328        """
329        Starts the connected card and enables triggering on the card (see command `M2CMD_CARD_START` in the manual)
330
331        Parameters
332        ----------
333        *args : int
334            flags that are send together with the start command
335        """
336
337        self.cmd(M2CMD_CARD_START, *args)

Starts the connected card and enables triggering on the card (see command M2CMD_CARD_START in the manual)

Parameters
  • *args (int): flags that are send together with the start command
def stop(self, *args: int) -> None:
339    def stop(self, *args : int) -> None:
340        """
341        Stops the connected card (see command `M2CMD_CARD_STOP` in the manual)
342
343        Parameters
344        ----------
345        *args : int
346            flags that are send together with the stop command (e.g. M2CMD_DATA_STOPDMA)
347        """
348
349        self.cmd(M2CMD_CARD_STOP, *args)

Stops the connected card (see command M2CMD_CARD_STOP in the manual)

Parameters
  • *args (int): flags that are send together with the stop command (e.g. M2CMD_DATA_STOPDMA)
def reset(self) -> None:
351    def reset(self) -> None:
352        """
353        Resets the connected device (see command `M2CMD_CARD_RESET` in the manual)
354        """
355
356        self.cmd(M2CMD_CARD_RESET)

Resets the connected device (see command M2CMD_CARD_RESET in the manual)

def write_setup(self, *args) -> None:
358    def write_setup(self, *args) -> None:
359        """
360        Writes of the configuration registers previously changed to the device (see command `M2CMD_CARD_WRITESETUP` in the manual)
361
362        Parameters
363        ----------
364        *args : int
365            flags that are set with the write command
366        """
367
368        self.cmd(M2CMD_CARD_WRITESETUP, *args)

Writes of the configuration registers previously changed to the device (see command M2CMD_CARD_WRITESETUP in the manual)

Parameters
  • *args (int): flags that are set with the write command
def register_list(self, register_list: List[dict[str, Union[int, float]]]) -> None:
370    def register_list(self, register_list : List[dict[str, Union[int, float]]]) -> None:
371        """
372        Writes a list with dictionaries, where each dictionary corresponds to a command (see the user manual of your device for all the available registers)
373    
374        Parameters
375        ----------
376        register_list : List[dict[str, Union[int, float]]]
377            The list of commands that needs to written to the specific registers of the card.
378        """
379
380        c_astParams = (ST_LIST_PARAM * 1024)()
381        astParams = ctypes.cast(c_astParams, ctypes.POINTER(ST_LIST_PARAM))
382        for i, register in enumerate(register_list):
383            astParams[i].lReg = register["lReg"]
384            astParams[i].lType = register["lType"]
385            if register["lType"] == TYPE_INT64:
386                astParams[i].Value.llValue = register["llValue"]
387            elif register["lType"] == TYPE_DOUBLE:
388                astParams[i].Value.dValue = register["dValue"]
389        self.set_ptr(SPC_REGISTER_LIST, astParams, len(register_list) * ctypes.sizeof(ST_LIST_PARAM))

Writes a list with dictionaries, where each dictionary corresponds to a command (see the user manual of your device for all the available registers)

Parameters
  • register_list (List[dict[str, Union[int, float]]]): The list of commands that needs to written to the specific registers of the card.
def get_i(self, register: int) -> int:
392    def get_i(self, register : int) -> int:
393        """
394        Get the integer value of a specific register of the card (see the user manual of your device for all the available registers)
395    
396        Parameters
397        ----------
398        register : int
399            The specific register that will be read from.
400        
401        Returns
402        -------
403        int
404            The value as stored in the specific register
405        """
406
407        self._check_closed()
408        return_value = int64(0)
409        dwErr = spcm_dwGetParam_i64(self._handle, register, byref(return_value))
410        self._check_error(dwErr)
411        return return_value.value

Get the integer value of a specific register of the card (see the user manual of your device for all the available registers)

Parameters
  • register (int): The specific register that will be read from.
Returns
  • int: The value as stored in the specific register
def get(self, register: int) -> int:
392    def get_i(self, register : int) -> int:
393        """
394        Get the integer value of a specific register of the card (see the user manual of your device for all the available registers)
395    
396        Parameters
397        ----------
398        register : int
399            The specific register that will be read from.
400        
401        Returns
402        -------
403        int
404            The value as stored in the specific register
405        """
406
407        self._check_closed()
408        return_value = int64(0)
409        dwErr = spcm_dwGetParam_i64(self._handle, register, byref(return_value))
410        self._check_error(dwErr)
411        return return_value.value

Alias of get_i

def get_d(self, register: int) -> float:
415    def get_d(self, register : int) -> float:
416        """
417        Get the float value of a specific register of the card (see the user manual of your device for all the available registers)
418    
419        Parameters
420        ----------
421        register : int
422            The specific register that will be read from.
423        
424        Returns
425        -------
426        float
427            The value as stored in the specific register
428        """
429
430        self._check_closed()
431        return_value = c_double(0)
432        self._check_error(spcm_dwGetParam_d64(self._handle, register, byref(return_value)))
433        return return_value.value

Get the float value of a specific register of the card (see the user manual of your device for all the available registers)

Parameters
  • register (int): The specific register that will be read from.
Returns
  • float: The value as stored in the specific register
def get_str(self, register: int) -> str:
435    def get_str(self, register : int) -> str:
436        """
437        Get the string value of a specific register of the card (see the user manual of your device for all the available registers)
438    
439        Parameters
440        ----------
441        register : int
442            The specific register that will be read from.
443        
444        Returns
445        -------
446        str
447            The value as stored in the specific register
448        """
449
450        self._check_closed()
451        return_value = create_string_buffer(self._str_len)
452        self._check_error(spcm_dwGetParam_ptr(self._handle, register, byref(return_value), self._str_len))
453        return return_value.value.decode('utf-8')

Get the string value of a specific register of the card (see the user manual of your device for all the available registers)

Parameters
  • register (int): The specific register that will be read from.
Returns
  • str: The value as stored in the specific register
def set_i(self, register: int, value: int) -> None:
455    def set_i(self, register : int, value : int) -> None:
456        """
457        Write the value of a specific register to the card (see the user manual of your device for all the available registers)
458    
459        Parameters
460        ----------
461        register : int
462            The specific register that will be written.
463        value : int
464            The value that is written to the card.
465        """
466
467        self._check_closed()
468        self._check_error(spcm_dwSetParam_i64(self._handle, register, value))

Write the value of a specific register to the card (see the user manual of your device for all the available registers)

Parameters
  • register (int): The specific register that will be written.
  • value (int): The value that is written to the card.
def set_d(self, register: int, value: float) -> None:
470    def set_d(self, register : int, value : float) -> None:
471        """
472        Write the value of a specific register to the card (see the user manual of your device for all the available registers)
473    
474        Parameters
475        ----------
476        register : int
477            The specific register that will be written.
478        value : float
479            The value that is written to the card.
480        """
481
482        self._check_closed()
483        self._check_error(spcm_dwSetParam_d64(self._handle, register, value))

Write the value of a specific register to the card (see the user manual of your device for all the available registers)

Parameters
  • register (int): The specific register that will be written.
  • value (float): The value that is written to the card.
def set_ptr(self, register: int, reference: ctypes.c_void_p, size: int) -> None:
485    def set_ptr(self, register : int, reference : c_void_p, size : int) -> None:
486        """
487        Use a memory segment to write to a specific register of the card (see the user manual of your device for all the available registers)
488    
489        Parameters
490        ----------
491        register : int
492            The specific register that will be read from.
493        reference : c_void_p
494            pointer to the memory segment
495        size : int
496            size of the memory segment
497        
498        Returns
499        -------
500        int
501            The value as stored in the specific register
502        """
503
504        self._check_closed()
505        self._check_error(spcm_dwSetParam_ptr(self._handle, register, reference, size))

Use a memory segment to write to a specific register of the card (see the user manual of your device for all the available registers)

Parameters
  • register (int): The specific register that will be read from.
  • reference (c_void_p): pointer to the memory segment
  • size (int): size of the memory segment
Returns
  • int: The value as stored in the specific register
def get_error_info(self) -> SpcmError:
530    def get_error_info(self) -> SpcmError:
531        """
532        Create an SpcmError object and store it in an object parameter
533    
534        Returns
535        ----------
536        SpcmError
537            the Error object containing the last error
538        """
539
540        self._last_error = SpcmError(self._handle)
541        return self._last_error

Create an SpcmError object and store it in an object parameter

Returns
  • SpcmError: the Error object containing the last error
def open_handle(self, device_identifier: str) -> None:
576    def open_handle(self, device_identifier : str) -> None:
577        """
578        Open a connection to the card and create a handle (see the user manual of your specific device on how to find out the device_identifier string)
579    
580        Parameters
581        ----------
582        device_identifier : str
583            The card identifier string (e.g. '/dev/spcm0' for a local device or 'TCPIP::192.168.1.10::inst0::INSTR' for a remote device)
584        """
585
586        self._handle = spcm_hOpen(create_string_buffer(bytes(device_identifier, 'utf-8')))
587        self._closed = False

Open a connection to the card and create a handle (see the user manual of your specific device on how to find out the device_identifier string)

Parameters
  • device_identifier (str): The card identifier string (e.g. '/dev/spcm0' for a local device or 'TCPIP::192.168.1.10::inst0::INSTR' for a remote device)
def close_handle(self) -> None:
589    def close_handle(self) -> None:
590        """
591        Close a connection to the card using the handle
592        """
593
594        spcm_vClose(self._handle)
595        self._handle = None
596        self._closed = True

Close a connection to the card using the handle

class Card(spcm.Device):
 15class Card(Device):
 16    """
 17    a high-level class to control Spectrum Instrumentation cards
 18
 19    For more information about what setups are available, please have a look at the user manual
 20    for your specific card.
 21
 22    """
 23
 24    _std_device_identifier : str = "/dev/spcm{}"
 25    _max_cards : int = 64
 26    
 27    _function_type : int = 0
 28    _card_type : int = 0
 29    _max_sample_value : int = 0
 30    _features : int = 0
 31    _ext_features : int = 0
 32    _demo_card : bool = False
 33    _product_name : str = ""
 34    _sn : int = 0
 35
 36
 37    def __enter__(self) -> 'Card':
 38        """
 39        Context manager entry function
 40
 41        Returns
 42        -------
 43        Card
 44            The card object
 45        
 46        Raises
 47        ------
 48        SpcmException
 49        """
 50        return super().__enter__()
 51
 52    def open(self, device_identifier : str = None) -> 'Card':
 53        """
 54        Open a connection to the card
 55
 56        Parameters
 57        ----------
 58        device_identifier : str = ""
 59            The device identifier of the card that needs to be opened
 60
 61        Returns
 62        -------
 63        Card
 64            The card object
 65
 66        Raises
 67        ------
 68        SpcmException
 69        """
 70
 71        if device_identifier is not None:
 72            return super().open(device_identifier=device_identifier)
 73        
 74        super().open()
 75
 76        # keyword arguments
 77        card_type = self._kwargs.get("card_type", 0)
 78        serial_number = self._kwargs.get("serial_number", 0)
 79
 80        if self.device_identifier == "":
 81            # No device identifier was given, so we need to find the first card
 82            self._handle = self.find(card_type=card_type, serial_number=serial_number)
 83            if not self._handle:
 84                if card_type:
 85                    raise SpcmException(text="No card found of right type")
 86                elif serial_number:
 87                    raise SpcmException(text="No card found with serial number: {}".format(serial_number))
 88            else:
 89                self._closed = False
 90        elif self._handle:
 91            if card_type != 0 and self.function_type() != card_type:
 92                raise SpcmException(text="The card with the given device identifier is not the correct type")
 93            elif serial_number != 0 and self.sn() != serial_number:
 94                raise SpcmException(text="The card with the given device identifier does not have the correct serial number")
 95
 96        # Get the function type of the card
 97        self._function_type = self.get_i(SPC_FNCTYPE)
 98        self._card_type = self.get_i(SPC_PCITYP)
 99        self._features = self.get_i(SPC_PCIFEATURES)
100        self._ext_features = self.get_i(SPC_PCIEXTFEATURES)
101        self._max_sample_value = self.get_i(SPC_MIINST_MAXADCVALUE)
102        self._demo_card = bool(self.get_i(SPC_MIINST_ISDEMOCARD))
103        self._product_name = self.get_str(SPC_PCITYP)
104        self._sn = self.get_i(SPC_PCISERIALNO)
105        
106        # Check python, driver and kernel version
107        if self._verbose:
108            print("Python version: {} on {}".format (platform.python_version(), platform.system()))
109            print("Driver version: {major}.{minor}.{build}".format(**self.drv_version()))
110            print("Kernel version: {major}.{minor}.{build}".format(**self.kernel_version()))
111            if self._handle:
112                print("Found '{}': {} sn {:05d}".format(self.device_identifier, self.product_name(), self.sn()))
113        
114        return self
115    
116    def __str__(self) -> str:
117        """
118        String representation of the card
119
120        Returns
121        -------
122        str
123            String representation of the card
124        """
125        return "Card: {} sn {:05d}".format(self.product_name(), self.sn())
126    __repr__ = __str__
127    
128    def find(self, card_type : int = 0, serial_number : int = 0) -> Union[bool, int]:
129        """
130        Find first card that is connected to the computer, with either the given card type or serial number
131
132        Parameters
133        ----------
134        card_type : int = 0
135            The function type of the card that needs to be found
136        serial_number : int = 0
137            The serial number of the card that needs to be found
138        
139        Returns
140        -------
141        Union[bool, int]
142            False if no card is found, otherwise the handle of the card
143
144        """
145        for nr in range(self._max_cards):
146            device_identifier = self._std_device_identifier.format(nr)
147            handle = spcm_hOpen(ctypes.create_string_buffer(bytes(device_identifier, 'utf-8')))
148            if handle:
149                self.device_identifier = device_identifier
150                return_value = ctypes.c_int64()
151                spcm_dwGetParam_i64(handle, SPC_FNCTYPE, ctypes.byref(return_value))
152                function_type = return_value.value
153                spcm_dwGetParam_i64(handle, SPC_PCISERIALNO, ctypes.byref(return_value))
154                sn = return_value.value
155                if card_type != 0 and (card_type & function_type) == function_type:
156                    return handle
157                elif sn != 0 and sn == serial_number:
158                    return handle
159                elif serial_number == 0 and card_type == 0:
160                    return handle
161                spcm_vClose(handle)
162        return False
163
164                
165    # High-level parameter functions, that use the low-level get and set function
166    def status(self) -> int:
167        """
168        Get the status of the card (see register `SPC_M2STATUS` in the manual)
169    
170        Returns
171        -------
172        int
173            The status of the card
174        """
175
176        return self.get_i(SPC_M2STATUS)
177    
178    def card_type(self) -> int:
179        """
180        Get the card type of the card (see register `SPC_PCITYP` in the manual)
181    
182        Returns
183        -------
184        int
185            The card type of the card
186        """
187
188        return self._card_type
189    
190    def series(self) -> int:
191        """
192        Get the series of the card (see register `SPC_PCITYP` and `TYP_SERIESMASK` in the manual)
193    
194        Returns
195        -------
196        int
197            The series of the card
198        """
199
200        return self.card_type() & TYP_SERIESMASK
201    
202    def family(self) -> int:
203        """
204        Get the family of the card (see register `SPC_PCITYP` and `TYP_FAMILYMASK` in the manual)
205    
206        Returns
207        -------
208        int
209            The family of the card
210        """
211
212        return (self.card_type() & TYP_FAMILYMASK) >> 8
213
214    def function_type(self) -> int:
215        """
216        Gives information about what type of card it is. (see register `SPC_FNCTYPE` in the manual)
217    
218        Returns
219        -------
220        int
221            The function type of the card
222
223            * SPCM_TYPE_AI = 1h - Analog input card (analog acquisition; the M2i.4028 and M2i.4038 also return this value)
224            * SPCM_TYPE_AO = 2h - Analog output card (arbitrary waveform generators)
225            * SPCM_TYPE_DI = 4h - Digital input card (logic analyzer card)
226            * SPCM_TYPE_DO = 8h - Digital output card (pattern generators)
227            * SPCM_TYPE_DIO = 10h - Digital I/O (input/output) card, where the direction is software selectable.
228        """
229
230        return self._function_type
231
232    def features(self) -> int:
233        """
234        Get the features of the card (see register `SPC_PCIFEATURES` in the manual)
235    
236        Returns
237        -------
238        int
239            The features of the card
240        """
241
242        return self._features
243    
244    def ext_features(self) -> int:
245        """
246        Get the extended features of the card (see register `SPC_PCIEXTFEATURES` in the manual)
247    
248        Returns
249        -------
250        int
251            The extended features of the card
252        """
253
254        return self._ext_features
255    
256    def starhub_card(self) -> bool:
257        """
258        Check if the card is a starhub card (see register `SPC_PCIFEATURES` in the manual)
259    
260        Returns
261        -------
262        bool
263            True if the card is the card that carriers a starhub, False otherwise
264        """
265
266        return bool(self._features & SPCM_FEAT_STARHUBXX_MASK)
267    
268    def num_modules(self) -> int:
269        """
270        Get the number of modules of the card (see register `SPC_MIINST_MODULES` in the manual)
271    
272        Returns
273        -------
274        int
275            The number of modules of the card
276        """
277
278        return self.get_i(SPC_MIINST_MODULES)
279
280    def channels_per_module(self) -> int:
281        """
282        Get the number of channels per module of the card (see register `SPC_MIINST_CHPERMODULE` in the manual)
283    
284        Returns
285        -------
286        int
287            The number of channels per module of the card
288        """
289
290        return self.get_i(SPC_MIINST_CHPERMODULE)
291    
292    def num_channels(self) -> int:
293        """
294        Get the number of channels of the card (= SPC_MIINST_MODULES * SPC_MIINST_CHPERMODULE)
295    
296        Returns
297        -------
298        int
299            The number of channels of the card
300        """
301
302        return self.num_modules() * self.channels_per_module()
303    
304    def is_demo_card(self) -> bool:
305        """
306        Check if the card is a demo card (see register `SPC_MIINST_ISDEMOCARD` in the manual)
307    
308        Returns
309        -------
310        bool
311            True if the card is a demo card, False otherwise
312        """
313
314        return self._demo_card
315
316    def card_mode(self, card_mode : int = None) -> int:
317        """
318        Set the card mode of the connected card (see register `SPC_CARDMODE` in the manual)
319        
320        Parameters
321        ----------
322        card_mode : int
323            the mode that the card needs to operate in
324
325        Returns
326        -------
327        int
328            the mode that the card operates in
329        """
330        
331        if card_mode is not None:
332            self.set_i(SPC_CARDMODE, card_mode)
333        return self.get_i(SPC_CARDMODE)
334
335    def product_name(self) -> str:
336        """
337        Get the product name of the card (see register `SPC_PCITYP` in the manual)
338    
339        Returns
340        -------
341        str
342            The product name of the connected card (e.g. M4i.6631-x8)
343        """
344
345        return self._product_name
346    
347    def sn(self) -> int:
348        """
349        Get the serial number of a product (see register `SPC_PCISERIALNO` in the manual)
350    
351        Returns
352        -------
353        int
354            The serial number of the connected card (e.g. 12345)
355        """
356
357        return self._sn
358
359    def active_channels(self) -> int:
360        """
361        Get the number of channels of the card (see register `SPC_CHCOUNT` in the manual)
362    
363        Returns
364        -------
365        int
366            The number of channels of the card
367        """
368
369        return self.get_i(SPC_CHCOUNT)
370
371    def bits_per_sample(self) -> int:
372        """
373        Get the number of bits per sample of the card (see register `SPC_MIINST_BITSPERSAMPLE` in the manual)
374    
375        Returns
376        -------
377        int
378            The number of bits per sample of the card
379        """
380
381        return self.get_i(SPC_MIINST_BITSPERSAMPLE)
382    
383    def bytes_per_sample(self) -> int:
384        """
385        Get the number of bytes per sample
386
387        Returns
388        -------
389        int
390            number of bytes per sample
391        """
392        return self.get_i(SPC_MIINST_BYTESPERSAMPLE)
393    
394    def max_sample_value(self) -> int:
395        """
396        Get the maximum ADC value of the card (see register `SPC_MIINST_MAXADCVALUE` in the manual)
397
398        Returns
399        -------
400        int
401            The maximum ADC value of the card
402        """
403
404        return self._max_sample_value
405    
406    def loops(self, loops : int = None) -> int:
407        """
408        Set the number of times the memory is replayed. If set to zero the generation will run continuously until it is 
409        stopped by the user.  (see register `SPC_LOOPS` in the manual)
410        
411        Parameters
412        ----------
413        loops : int
414            the number of loops that the card should perform
415        """
416
417        if loops is not None:
418            self.set_i(SPC_LOOPS, loops)
419        return self.get_i(SPC_LOOPS)

a high-level class to control Spectrum Instrumentation cards

For more information about what setups are available, please have a look at the user manual for your specific card.

def open(self, device_identifier: str = None) -> Card:
 52    def open(self, device_identifier : str = None) -> 'Card':
 53        """
 54        Open a connection to the card
 55
 56        Parameters
 57        ----------
 58        device_identifier : str = ""
 59            The device identifier of the card that needs to be opened
 60
 61        Returns
 62        -------
 63        Card
 64            The card object
 65
 66        Raises
 67        ------
 68        SpcmException
 69        """
 70
 71        if device_identifier is not None:
 72            return super().open(device_identifier=device_identifier)
 73        
 74        super().open()
 75
 76        # keyword arguments
 77        card_type = self._kwargs.get("card_type", 0)
 78        serial_number = self._kwargs.get("serial_number", 0)
 79
 80        if self.device_identifier == "":
 81            # No device identifier was given, so we need to find the first card
 82            self._handle = self.find(card_type=card_type, serial_number=serial_number)
 83            if not self._handle:
 84                if card_type:
 85                    raise SpcmException(text="No card found of right type")
 86                elif serial_number:
 87                    raise SpcmException(text="No card found with serial number: {}".format(serial_number))
 88            else:
 89                self._closed = False
 90        elif self._handle:
 91            if card_type != 0 and self.function_type() != card_type:
 92                raise SpcmException(text="The card with the given device identifier is not the correct type")
 93            elif serial_number != 0 and self.sn() != serial_number:
 94                raise SpcmException(text="The card with the given device identifier does not have the correct serial number")
 95
 96        # Get the function type of the card
 97        self._function_type = self.get_i(SPC_FNCTYPE)
 98        self._card_type = self.get_i(SPC_PCITYP)
 99        self._features = self.get_i(SPC_PCIFEATURES)
100        self._ext_features = self.get_i(SPC_PCIEXTFEATURES)
101        self._max_sample_value = self.get_i(SPC_MIINST_MAXADCVALUE)
102        self._demo_card = bool(self.get_i(SPC_MIINST_ISDEMOCARD))
103        self._product_name = self.get_str(SPC_PCITYP)
104        self._sn = self.get_i(SPC_PCISERIALNO)
105        
106        # Check python, driver and kernel version
107        if self._verbose:
108            print("Python version: {} on {}".format (platform.python_version(), platform.system()))
109            print("Driver version: {major}.{minor}.{build}".format(**self.drv_version()))
110            print("Kernel version: {major}.{minor}.{build}".format(**self.kernel_version()))
111            if self._handle:
112                print("Found '{}': {} sn {:05d}".format(self.device_identifier, self.product_name(), self.sn()))
113        
114        return self

Open a connection to the card

Parameters
  • device_identifier (str = ""): The device identifier of the card that needs to be opened
Returns
  • Card: The card object
Raises
  • SpcmException
def find(self, card_type: int = 0, serial_number: int = 0) -> Union[bool, int]:
128    def find(self, card_type : int = 0, serial_number : int = 0) -> Union[bool, int]:
129        """
130        Find first card that is connected to the computer, with either the given card type or serial number
131
132        Parameters
133        ----------
134        card_type : int = 0
135            The function type of the card that needs to be found
136        serial_number : int = 0
137            The serial number of the card that needs to be found
138        
139        Returns
140        -------
141        Union[bool, int]
142            False if no card is found, otherwise the handle of the card
143
144        """
145        for nr in range(self._max_cards):
146            device_identifier = self._std_device_identifier.format(nr)
147            handle = spcm_hOpen(ctypes.create_string_buffer(bytes(device_identifier, 'utf-8')))
148            if handle:
149                self.device_identifier = device_identifier
150                return_value = ctypes.c_int64()
151                spcm_dwGetParam_i64(handle, SPC_FNCTYPE, ctypes.byref(return_value))
152                function_type = return_value.value
153                spcm_dwGetParam_i64(handle, SPC_PCISERIALNO, ctypes.byref(return_value))
154                sn = return_value.value
155                if card_type != 0 and (card_type & function_type) == function_type:
156                    return handle
157                elif sn != 0 and sn == serial_number:
158                    return handle
159                elif serial_number == 0 and card_type == 0:
160                    return handle
161                spcm_vClose(handle)
162        return False

Find first card that is connected to the computer, with either the given card type or serial number

Parameters
  • card_type (int = 0): The function type of the card that needs to be found
  • serial_number (int = 0): The serial number of the card that needs to be found
Returns
  • Union[bool, int]: False if no card is found, otherwise the handle of the card
def status(self) -> int:
166    def status(self) -> int:
167        """
168        Get the status of the card (see register `SPC_M2STATUS` in the manual)
169    
170        Returns
171        -------
172        int
173            The status of the card
174        """
175
176        return self.get_i(SPC_M2STATUS)

Get the status of the card (see register SPC_M2STATUS in the manual)

Returns
  • int: The status of the card
def card_type(self) -> int:
178    def card_type(self) -> int:
179        """
180        Get the card type of the card (see register `SPC_PCITYP` in the manual)
181    
182        Returns
183        -------
184        int
185            The card type of the card
186        """
187
188        return self._card_type

Get the card type of the card (see register SPC_PCITYP in the manual)

Returns
  • int: The card type of the card
def series(self) -> int:
190    def series(self) -> int:
191        """
192        Get the series of the card (see register `SPC_PCITYP` and `TYP_SERIESMASK` in the manual)
193    
194        Returns
195        -------
196        int
197            The series of the card
198        """
199
200        return self.card_type() & TYP_SERIESMASK

Get the series of the card (see register SPC_PCITYP and TYP_SERIESMASK in the manual)

Returns
  • int: The series of the card
def family(self) -> int:
202    def family(self) -> int:
203        """
204        Get the family of the card (see register `SPC_PCITYP` and `TYP_FAMILYMASK` in the manual)
205    
206        Returns
207        -------
208        int
209            The family of the card
210        """
211
212        return (self.card_type() & TYP_FAMILYMASK) >> 8

Get the family of the card (see register SPC_PCITYP and TYP_FAMILYMASK in the manual)

Returns
  • int: The family of the card
def function_type(self) -> int:
214    def function_type(self) -> int:
215        """
216        Gives information about what type of card it is. (see register `SPC_FNCTYPE` in the manual)
217    
218        Returns
219        -------
220        int
221            The function type of the card
222
223            * SPCM_TYPE_AI = 1h - Analog input card (analog acquisition; the M2i.4028 and M2i.4038 also return this value)
224            * SPCM_TYPE_AO = 2h - Analog output card (arbitrary waveform generators)
225            * SPCM_TYPE_DI = 4h - Digital input card (logic analyzer card)
226            * SPCM_TYPE_DO = 8h - Digital output card (pattern generators)
227            * SPCM_TYPE_DIO = 10h - Digital I/O (input/output) card, where the direction is software selectable.
228        """
229
230        return self._function_type

Gives information about what type of card it is. (see register SPC_FNCTYPE in the manual)

Returns
  • int: The function type of the card

    • SPCM_TYPE_AI = 1h - Analog input card (analog acquisition; the M2i.4028 and M2i.4038 also return this value)
    • SPCM_TYPE_AO = 2h - Analog output card (arbitrary waveform generators)
    • SPCM_TYPE_DI = 4h - Digital input card (logic analyzer card)
    • SPCM_TYPE_DO = 8h - Digital output card (pattern generators)
    • SPCM_TYPE_DIO = 10h - Digital I/O (input/output) card, where the direction is software selectable.
def features(self) -> int:
232    def features(self) -> int:
233        """
234        Get the features of the card (see register `SPC_PCIFEATURES` in the manual)
235    
236        Returns
237        -------
238        int
239            The features of the card
240        """
241
242        return self._features

Get the features of the card (see register SPC_PCIFEATURES in the manual)

Returns
  • int: The features of the card
def ext_features(self) -> int:
244    def ext_features(self) -> int:
245        """
246        Get the extended features of the card (see register `SPC_PCIEXTFEATURES` in the manual)
247    
248        Returns
249        -------
250        int
251            The extended features of the card
252        """
253
254        return self._ext_features

Get the extended features of the card (see register SPC_PCIEXTFEATURES in the manual)

Returns
  • int: The extended features of the card
def starhub_card(self) -> bool:
256    def starhub_card(self) -> bool:
257        """
258        Check if the card is a starhub card (see register `SPC_PCIFEATURES` in the manual)
259    
260        Returns
261        -------
262        bool
263            True if the card is the card that carriers a starhub, False otherwise
264        """
265
266        return bool(self._features & SPCM_FEAT_STARHUBXX_MASK)

Check if the card is a starhub card (see register SPC_PCIFEATURES in the manual)

Returns
  • bool: True if the card is the card that carriers a starhub, False otherwise
def num_modules(self) -> int:
268    def num_modules(self) -> int:
269        """
270        Get the number of modules of the card (see register `SPC_MIINST_MODULES` in the manual)
271    
272        Returns
273        -------
274        int
275            The number of modules of the card
276        """
277
278        return self.get_i(SPC_MIINST_MODULES)

Get the number of modules of the card (see register SPC_MIINST_MODULES in the manual)

Returns
  • int: The number of modules of the card
def channels_per_module(self) -> int:
280    def channels_per_module(self) -> int:
281        """
282        Get the number of channels per module of the card (see register `SPC_MIINST_CHPERMODULE` in the manual)
283    
284        Returns
285        -------
286        int
287            The number of channels per module of the card
288        """
289
290        return self.get_i(SPC_MIINST_CHPERMODULE)

Get the number of channels per module of the card (see register SPC_MIINST_CHPERMODULE in the manual)

Returns
  • int: The number of channels per module of the card
def num_channels(self) -> int:
292    def num_channels(self) -> int:
293        """
294        Get the number of channels of the card (= SPC_MIINST_MODULES * SPC_MIINST_CHPERMODULE)
295    
296        Returns
297        -------
298        int
299            The number of channels of the card
300        """
301
302        return self.num_modules() * self.channels_per_module()

Get the number of channels of the card (= SPC_MIINST_MODULES * SPC_MIINST_CHPERMODULE)

Returns
  • int: The number of channels of the card
def is_demo_card(self) -> bool:
304    def is_demo_card(self) -> bool:
305        """
306        Check if the card is a demo card (see register `SPC_MIINST_ISDEMOCARD` in the manual)
307    
308        Returns
309        -------
310        bool
311            True if the card is a demo card, False otherwise
312        """
313
314        return self._demo_card

Check if the card is a demo card (see register SPC_MIINST_ISDEMOCARD in the manual)

Returns
  • bool: True if the card is a demo card, False otherwise
def card_mode(self, card_mode: int = None) -> int:
316    def card_mode(self, card_mode : int = None) -> int:
317        """
318        Set the card mode of the connected card (see register `SPC_CARDMODE` in the manual)
319        
320        Parameters
321        ----------
322        card_mode : int
323            the mode that the card needs to operate in
324
325        Returns
326        -------
327        int
328            the mode that the card operates in
329        """
330        
331        if card_mode is not None:
332            self.set_i(SPC_CARDMODE, card_mode)
333        return self.get_i(SPC_CARDMODE)

Set the card mode of the connected card (see register SPC_CARDMODE in the manual)

Parameters
  • card_mode (int): the mode that the card needs to operate in
Returns
  • int: the mode that the card operates in
def product_name(self) -> str:
335    def product_name(self) -> str:
336        """
337        Get the product name of the card (see register `SPC_PCITYP` in the manual)
338    
339        Returns
340        -------
341        str
342            The product name of the connected card (e.g. M4i.6631-x8)
343        """
344
345        return self._product_name

Get the product name of the card (see register SPC_PCITYP in the manual)

Returns
  • str: The product name of the connected card (e.g. M4i.6631-x8)
def sn(self) -> int:
347    def sn(self) -> int:
348        """
349        Get the serial number of a product (see register `SPC_PCISERIALNO` in the manual)
350    
351        Returns
352        -------
353        int
354            The serial number of the connected card (e.g. 12345)
355        """
356
357        return self._sn

Get the serial number of a product (see register SPC_PCISERIALNO in the manual)

Returns
  • int: The serial number of the connected card (e.g. 12345)
def active_channels(self) -> int:
359    def active_channels(self) -> int:
360        """
361        Get the number of channels of the card (see register `SPC_CHCOUNT` in the manual)
362    
363        Returns
364        -------
365        int
366            The number of channels of the card
367        """
368
369        return self.get_i(SPC_CHCOUNT)

Get the number of channels of the card (see register SPC_CHCOUNT in the manual)

Returns
  • int: The number of channels of the card
def bits_per_sample(self) -> int:
371    def bits_per_sample(self) -> int:
372        """
373        Get the number of bits per sample of the card (see register `SPC_MIINST_BITSPERSAMPLE` in the manual)
374    
375        Returns
376        -------
377        int
378            The number of bits per sample of the card
379        """
380
381        return self.get_i(SPC_MIINST_BITSPERSAMPLE)

Get the number of bits per sample of the card (see register SPC_MIINST_BITSPERSAMPLE in the manual)

Returns
  • int: The number of bits per sample of the card
def bytes_per_sample(self) -> int:
383    def bytes_per_sample(self) -> int:
384        """
385        Get the number of bytes per sample
386
387        Returns
388        -------
389        int
390            number of bytes per sample
391        """
392        return self.get_i(SPC_MIINST_BYTESPERSAMPLE)

Get the number of bytes per sample

Returns
  • int: number of bytes per sample
def max_sample_value(self) -> int:
394    def max_sample_value(self) -> int:
395        """
396        Get the maximum ADC value of the card (see register `SPC_MIINST_MAXADCVALUE` in the manual)
397
398        Returns
399        -------
400        int
401            The maximum ADC value of the card
402        """
403
404        return self._max_sample_value

Get the maximum ADC value of the card (see register SPC_MIINST_MAXADCVALUE in the manual)

Returns
  • int: The maximum ADC value of the card
def loops(self, loops: int = None) -> int:
406    def loops(self, loops : int = None) -> int:
407        """
408        Set the number of times the memory is replayed. If set to zero the generation will run continuously until it is 
409        stopped by the user.  (see register `SPC_LOOPS` in the manual)
410        
411        Parameters
412        ----------
413        loops : int
414            the number of loops that the card should perform
415        """
416
417        if loops is not None:
418            self.set_i(SPC_LOOPS, loops)
419        return self.get_i(SPC_LOOPS)

Set the number of times the memory is replayed. If set to zero the generation will run continuously until it is stopped by the user. (see register SPC_LOOPS in the manual)

Parameters
  • loops (int): the number of loops that the card should perform
class Sync(spcm.Device):
  8class Sync(Device):
  9    """a class to control Spectrum Instrumentation Starhub synchronization devices
 10
 11    For more information about what setups are available, please have a look at the user manual
 12    for your specific Starhub.
 13    
 14    Exceptions
 15    ----------
 16    SpcmException
 17    SpcmTimeout
 18    """
 19
 20    def enable(self, enable : int = None) -> int:
 21        """
 22        Enable or disable the Starthub (see register 'SPC_SYNC_ENABLEMASK' in chapter `Star-Hub` in the manual)
 23
 24        Parameters
 25        ----------
 26        enable : int or bool
 27            enable or disable the Starthub
 28        """
 29
 30        if enable is not None:
 31            enable_mask = 0
 32            if isinstance(enable, bool):
 33                num_cards = self.sync_count()
 34                enable_mask = ((1 << num_cards) - 1) if enable else 0
 35            elif isinstance(enable, int):
 36                enable_mask = enable
 37            else:
 38                raise ValueError("The enable parameter must be a boolean or an integer")
 39            self.set_i(SPC_SYNC_ENABLEMASK, enable_mask)
 40        return self.get_i(SPC_SYNC_ENABLEMASK)
 41    
 42    def num_connectors(self) -> int:
 43        """
 44        Number of connectors that the Star-Hub offers at max. (see register 'SPC_SYNC_READ_NUMCONNECTORS' in chapter `Star-Hub` in the manual)
 45
 46        Returns
 47        -------
 48        int
 49            number of connectors on the StarHub
 50        """
 51
 52        return self.get_i(SPC_SYNC_READ_NUMCONNECTORS)
 53    
 54    def sync_count(self) -> int:
 55        """
 56        Number of cards that are connected to this Star-Hub (see register 'SPC_SYNC_READ_SYNCCOUNT' in chapter `Star-Hub` in the manual)
 57
 58        Returns
 59        -------
 60        int
 61            number of synchronized cards
 62        """
 63
 64        return self.get_i(SPC_SYNC_READ_SYNCCOUNT)
 65    
 66    def card_index(self, index) -> int:
 67        """
 68        Index of the card that is connected to the Star-Hub at the given local index (see register 'SPC_SYNC_READ_CARDIDX0' in chapter `Star-Hub` in the manual)
 69
 70        Parameters
 71        ----------
 72        index : int
 73            connector index
 74
 75        Returns
 76        -------
 77        int
 78            card index
 79        """
 80
 81        return self.get_i(SPC_SYNC_READ_CARDIDX0 + index)
 82    
 83    def cable_connection(self, index) -> int:
 84        """
 85        Returns the index of the cable connection that is used for the logical connection `index`. (see register 'SPC_SYNC_READ_CABLECON0' in chapter `Star-Hub` in the manual)
 86        The cable connections can be seen printed on the PCB of the star-hub. Use these cable connection information in case 
 87        that there are hardware failures with the star-hub cabeling.
 88
 89        Parameters
 90        ----------
 91        index : int
 92            connector index
 93
 94        Returns
 95        -------
 96        int
 97            cable index
 98        """
 99
100        return self.get_i(SPC_SYNC_READ_CABLECON0 + index)

a class to control Spectrum Instrumentation Starhub synchronization devices

For more information about what setups are available, please have a look at the user manual for your specific Starhub.

Exceptions

SpcmException SpcmTimeout

def enable(self, enable: int = None) -> int:
20    def enable(self, enable : int = None) -> int:
21        """
22        Enable or disable the Starthub (see register 'SPC_SYNC_ENABLEMASK' in chapter `Star-Hub` in the manual)
23
24        Parameters
25        ----------
26        enable : int or bool
27            enable or disable the Starthub
28        """
29
30        if enable is not None:
31            enable_mask = 0
32            if isinstance(enable, bool):
33                num_cards = self.sync_count()
34                enable_mask = ((1 << num_cards) - 1) if enable else 0
35            elif isinstance(enable, int):
36                enable_mask = enable
37            else:
38                raise ValueError("The enable parameter must be a boolean or an integer")
39            self.set_i(SPC_SYNC_ENABLEMASK, enable_mask)
40        return self.get_i(SPC_SYNC_ENABLEMASK)

Enable or disable the Starthub (see register 'SPC_SYNC_ENABLEMASK' in chapter Star-Hub in the manual)

Parameters
  • enable (int or bool): enable or disable the Starthub
def num_connectors(self) -> int:
42    def num_connectors(self) -> int:
43        """
44        Number of connectors that the Star-Hub offers at max. (see register 'SPC_SYNC_READ_NUMCONNECTORS' in chapter `Star-Hub` in the manual)
45
46        Returns
47        -------
48        int
49            number of connectors on the StarHub
50        """
51
52        return self.get_i(SPC_SYNC_READ_NUMCONNECTORS)

Number of connectors that the Star-Hub offers at max. (see register 'SPC_SYNC_READ_NUMCONNECTORS' in chapter Star-Hub in the manual)

Returns
  • int: number of connectors on the StarHub
def sync_count(self) -> int:
54    def sync_count(self) -> int:
55        """
56        Number of cards that are connected to this Star-Hub (see register 'SPC_SYNC_READ_SYNCCOUNT' in chapter `Star-Hub` in the manual)
57
58        Returns
59        -------
60        int
61            number of synchronized cards
62        """
63
64        return self.get_i(SPC_SYNC_READ_SYNCCOUNT)

Number of cards that are connected to this Star-Hub (see register 'SPC_SYNC_READ_SYNCCOUNT' in chapter Star-Hub in the manual)

Returns
  • int: number of synchronized cards
def card_index(self, index) -> int:
66    def card_index(self, index) -> int:
67        """
68        Index of the card that is connected to the Star-Hub at the given local index (see register 'SPC_SYNC_READ_CARDIDX0' in chapter `Star-Hub` in the manual)
69
70        Parameters
71        ----------
72        index : int
73            connector index
74
75        Returns
76        -------
77        int
78            card index
79        """
80
81        return self.get_i(SPC_SYNC_READ_CARDIDX0 + index)

Index of the card that is connected to the Star-Hub at the given local index (see register 'SPC_SYNC_READ_CARDIDX0' in chapter Star-Hub in the manual)

Parameters
  • index (int): connector index
Returns
  • int: card index
def cable_connection(self, index) -> int:
 83    def cable_connection(self, index) -> int:
 84        """
 85        Returns the index of the cable connection that is used for the logical connection `index`. (see register 'SPC_SYNC_READ_CABLECON0' in chapter `Star-Hub` in the manual)
 86        The cable connections can be seen printed on the PCB of the star-hub. Use these cable connection information in case 
 87        that there are hardware failures with the star-hub cabeling.
 88
 89        Parameters
 90        ----------
 91        index : int
 92            connector index
 93
 94        Returns
 95        -------
 96        int
 97            cable index
 98        """
 99
100        return self.get_i(SPC_SYNC_READ_CABLECON0 + index)

Returns the index of the cable connection that is used for the logical connection index. (see register 'SPC_SYNC_READ_CABLECON0' in chapter Star-Hub in the manual) The cable connections can be seen printed on the PCB of the star-hub. Use these cable connection information in case that there are hardware failures with the star-hub cabeling.

Parameters
  • index (int): connector index
Returns
  • int: cable index
class CardStack(contextlib.ExitStack):
 13class CardStack(ExitStack):
 14    """
 15    A context manager object for handling multiple Card objects with or without a Sync object
 16 
 17    Parameters
 18    ----------
 19    cards : list[Card]
 20        a list of card objects that is managed by the context manager
 21    sync : Sync
 22        an object for handling the synchronization of cards
 23    sync_card : Card
 24        a card object that is used for synchronization
 25    sync_id : int
 26        the index of the sync card in the list of cards
 27    is_synced : bool
 28        a boolean that indicates if the cards are synchronized
 29    """
 30
 31    cards : list[Card] = []
 32    sync : Sync = None
 33    sync_card : Card = None
 34    sync_id : int = -1
 35    is_synced : bool = False
 36
 37    def __init__(self, card_identifiers : list[str] = [], sync_identifier : str = "", find_sync : bool = False) -> None:
 38        """
 39        Initialize the CardStack object with a list of card identifiers and a sync identifier
 40
 41        Parameters
 42        ----------
 43        card_identifiers : list[str] = []
 44            a list of strings that represent the VISA strings of the cards
 45        sync_identifier : str = ""
 46            a string that represents the VISA string of the sync card
 47        find_sync : bool = False
 48            a boolean that indicates if the sync card should be found automatically
 49        """
 50
 51        super().__init__()
 52        # Handle card objects
 53        self.cards = [self.enter_context(Card(identifier)) for identifier in card_identifiers]
 54        if find_sync:
 55            for id, card in enumerate(self.cards):
 56                if card.starhub_card():
 57                    self.sync_card = card
 58                    self.sync_id = id
 59                    self.is_synced = True
 60                    break
 61        if sync_identifier and (not find_sync or self.is_synced):
 62            self.sync = self.enter_context(Sync(sync_identifier))
 63            self.is_synced = bool(self.sync)
 64    
 65    def __bool__(self) -> bool:
 66        """Checks if all defined cards are connected"""
 67        connected = True
 68        for card in self.cards:
 69            connected &= bool(card)
 70        return connected
 71    
 72    def synched(self):
 73        """Checks if the sync card is connected
 74        """
 75        return bool(self.is_synced)
 76    
 77    def start(self, *args) -> None:
 78        """
 79        Start all cards
 80
 81        Parameters
 82        ----------
 83        args : list
 84            a list of arguments that will be passed to the start method of the cards
 85        """
 86        
 87        if self.sync:
 88            self.sync.start(*args)
 89        else:
 90            for card in self.cards:
 91                card.start(*args)
 92    
 93    def stop(self, *args) -> None:
 94        """
 95        Stop all cards
 96
 97        Parameters
 98        ----------
 99        args : list
100            a list of arguments that will be passed to the stop method of the cards
101        """
102        
103        if self.sync:
104            self.sync.stop(*args)
105        else:
106            for card in self.cards:
107                card.stop(*args)
108    
109    def reset(self, *args) -> None:
110        """
111        Reset all cards
112
113        Parameters
114        ----------
115        args : list
116            a list of arguments that will be passed to the reset method of the cards
117        """
118        
119        if self.sync:
120            self.sync.reset(*args)
121        else:
122            for card in self.cards:
123                card.reset(*args)
124    
125    def force_trigger(self, *args) -> None:
126        """
127        Force trigger on all cards
128
129        Parameters
130        ----------
131        args : list
132            a list of arguments that will be passed with the force trigger command for the cards
133        """
134        
135        # TODO: the force trigger needs to be correctly implemented in the driver
136        if self.sync_card:
137            self.sync_card.cmd(M2CMD_CARD_FORCETRIGGER, *args)
138        elif self.sync:
139            # self.sync.cmd(M2CMD_CARD_FORCETRIGGER, *args)
140            self.cards[0].cmd(M2CMD_CARD_FORCETRIGGER, *args)
141        else:
142            for card in self.cards:
143                card.cmd(M2CMD_CARD_FORCETRIGGER, *args)
144
145    def sync_enable(self, enable : int = True) -> int:
146        """
147        Enable synchronization on all cards
148
149        Parameters
150        ----------
151        enable : int or bool
152            a boolean or integer mask to enable or disable the synchronization of different channels
153        
154        Returns
155        -------
156        int
157            the mask of the enabled channels
158        
159        Raises
160        ------
161        ValueError
162            The enable parameter must be a boolean or an integer
163        SpcmException
164            No sync card avaliable to enable synchronization on the cards
165        """
166        
167        if self.sync:
168            return self.sync.enable(enable)
169        else:
170            raise SpcmException("No sync card avaliable to enable synchronization on the cards")
171    
172    
173    @staticmethod
174    def id_to_ip(device_identifier : str) -> str:
175        """
176        Returns the IP address of the Netbox using the device identifier
177
178        Parameters
179        ----------
180        device_identifier : str
181            The device identifier of the Netbox
182
183        Returns
184        -------
185        str
186            The IP address of the Netbox
187        """
188        ip = device_identifier
189        ip = ip[ip.find('::') + 2:]
190        ip = ip[:ip.find ('::')]
191        return ip
192    
193    @staticmethod
194    def discover(max_num_remote_cards : int = 50, max_visa_string_len : int = 256, max_idn_string_len : int = 256, timeout_ms : int = 5000) -> dict[list[str]]:
195        """
196        Do a discovery of the cards connected through a network
197
198        Parameters
199        ----------
200        max_num_remote_cards : int = 50
201            the maximum number of remote cards that can be discovered
202        max_visa_string_len : int = 256
203            the maximum length of the VISA string
204        max_idn_string_len : int = 256
205            the maximum length of the IDN string
206        timeout_ms : int = 5000
207            the timeout in milliseconds for the discovery process
208
209        Returns
210        -------
211        CardStack
212            a stack object with all the discovered cards
213        
214        Raises
215        ------
216        SpcmException
217            No Spectrum devices found
218        """
219
220        visa = (spcm_core.c_char_p * max_num_remote_cards)()
221        for i in range(max_num_remote_cards):
222            visa[i] = spcm_core.cast(spcm_core.create_string_buffer(max_visa_string_len), spcm_core.c_char_p)
223        spcm_core.spcm_dwDiscovery (visa, spcm_core.uint32(max_num_remote_cards), spcm_core.uint32(max_visa_string_len), spcm_core.uint32(timeout_ms))
224
225        # ----- check from which manufacturer the devices are -----
226        idn = (spcm_core.c_char_p * max_num_remote_cards)()
227        for i in range(max_num_remote_cards):
228            idn[i] = spcm_core.cast(spcm_core.create_string_buffer(max_idn_string_len), spcm_core.c_char_p)
229        spcm_core.spcm_dwSendIDNRequest (idn, spcm_core.uint32(max_num_remote_cards), spcm_core.uint32(max_idn_string_len))
230
231        # ----- store VISA strings for all discovered cards and open them afterwards -----
232        list_spectrum_devices = {}
233        for (id, visa) in zip(idn, visa):
234            if not id:
235                break
236
237            if id.decode('utf-8').startswith("Spectrum GmbH,"):
238                ip = __class__.id_to_ip(visa.decode("utf-8"))
239                if ip in list_spectrum_devices:
240                    list_spectrum_devices[ip].append(visa.decode("utf-8"))
241                else:
242                    list_spectrum_devices[ip] = [visa.decode("utf-8")]
243        
244        if not list_spectrum_devices:
245            raise SpcmException("No Spectrum devices found")
246        
247        return list_spectrum_devices

A context manager object for handling multiple Card objects with or without a Sync object

Parameters
  • cards (list[Card]): a list of card objects that is managed by the context manager
  • sync (Sync): an object for handling the synchronization of cards
  • sync_card (Card): a card object that is used for synchronization
  • sync_id (int): the index of the sync card in the list of cards
  • is_synced (bool): a boolean that indicates if the cards are synchronized
CardStack( card_identifiers: list[str] = [], sync_identifier: str = '', find_sync: bool = False)
37    def __init__(self, card_identifiers : list[str] = [], sync_identifier : str = "", find_sync : bool = False) -> None:
38        """
39        Initialize the CardStack object with a list of card identifiers and a sync identifier
40
41        Parameters
42        ----------
43        card_identifiers : list[str] = []
44            a list of strings that represent the VISA strings of the cards
45        sync_identifier : str = ""
46            a string that represents the VISA string of the sync card
47        find_sync : bool = False
48            a boolean that indicates if the sync card should be found automatically
49        """
50
51        super().__init__()
52        # Handle card objects
53        self.cards = [self.enter_context(Card(identifier)) for identifier in card_identifiers]
54        if find_sync:
55            for id, card in enumerate(self.cards):
56                if card.starhub_card():
57                    self.sync_card = card
58                    self.sync_id = id
59                    self.is_synced = True
60                    break
61        if sync_identifier and (not find_sync or self.is_synced):
62            self.sync = self.enter_context(Sync(sync_identifier))
63            self.is_synced = bool(self.sync)

Initialize the CardStack object with a list of card identifiers and a sync identifier

Parameters
  • card_identifiers (list[str] = []): a list of strings that represent the VISA strings of the cards
  • sync_identifier (str = ""): a string that represents the VISA string of the sync card
  • find_sync (bool = False): a boolean that indicates if the sync card should be found automatically
cards: list[Card] = []
sync: Sync = None
sync_card: Card = None
sync_id: int = -1
is_synced: bool = False
def synched(self):
72    def synched(self):
73        """Checks if the sync card is connected
74        """
75        return bool(self.is_synced)

Checks if the sync card is connected

def start(self, *args) -> None:
77    def start(self, *args) -> None:
78        """
79        Start all cards
80
81        Parameters
82        ----------
83        args : list
84            a list of arguments that will be passed to the start method of the cards
85        """
86        
87        if self.sync:
88            self.sync.start(*args)
89        else:
90            for card in self.cards:
91                card.start(*args)

Start all cards

Parameters
  • args (list): a list of arguments that will be passed to the start method of the cards
def stop(self, *args) -> None:
 93    def stop(self, *args) -> None:
 94        """
 95        Stop all cards
 96
 97        Parameters
 98        ----------
 99        args : list
100            a list of arguments that will be passed to the stop method of the cards
101        """
102        
103        if self.sync:
104            self.sync.stop(*args)
105        else:
106            for card in self.cards:
107                card.stop(*args)

Stop all cards

Parameters
  • args (list): a list of arguments that will be passed to the stop method of the cards
def reset(self, *args) -> None:
109    def reset(self, *args) -> None:
110        """
111        Reset all cards
112
113        Parameters
114        ----------
115        args : list
116            a list of arguments that will be passed to the reset method of the cards
117        """
118        
119        if self.sync:
120            self.sync.reset(*args)
121        else:
122            for card in self.cards:
123                card.reset(*args)

Reset all cards

Parameters
  • args (list): a list of arguments that will be passed to the reset method of the cards
def force_trigger(self, *args) -> None:
125    def force_trigger(self, *args) -> None:
126        """
127        Force trigger on all cards
128
129        Parameters
130        ----------
131        args : list
132            a list of arguments that will be passed with the force trigger command for the cards
133        """
134        
135        # TODO: the force trigger needs to be correctly implemented in the driver
136        if self.sync_card:
137            self.sync_card.cmd(M2CMD_CARD_FORCETRIGGER, *args)
138        elif self.sync:
139            # self.sync.cmd(M2CMD_CARD_FORCETRIGGER, *args)
140            self.cards[0].cmd(M2CMD_CARD_FORCETRIGGER, *args)
141        else:
142            for card in self.cards:
143                card.cmd(M2CMD_CARD_FORCETRIGGER, *args)

Force trigger on all cards

Parameters
  • args (list): a list of arguments that will be passed with the force trigger command for the cards
def sync_enable(self, enable: int = True) -> int:
145    def sync_enable(self, enable : int = True) -> int:
146        """
147        Enable synchronization on all cards
148
149        Parameters
150        ----------
151        enable : int or bool
152            a boolean or integer mask to enable or disable the synchronization of different channels
153        
154        Returns
155        -------
156        int
157            the mask of the enabled channels
158        
159        Raises
160        ------
161        ValueError
162            The enable parameter must be a boolean or an integer
163        SpcmException
164            No sync card avaliable to enable synchronization on the cards
165        """
166        
167        if self.sync:
168            return self.sync.enable(enable)
169        else:
170            raise SpcmException("No sync card avaliable to enable synchronization on the cards")

Enable synchronization on all cards

Parameters
  • enable (int or bool): a boolean or integer mask to enable or disable the synchronization of different channels
Returns
  • int: the mask of the enabled channels
Raises
  • ValueError: The enable parameter must be a boolean or an integer
  • SpcmException: No sync card avaliable to enable synchronization on the cards
@staticmethod
def id_to_ip(device_identifier: str) -> str:
173    @staticmethod
174    def id_to_ip(device_identifier : str) -> str:
175        """
176        Returns the IP address of the Netbox using the device identifier
177
178        Parameters
179        ----------
180        device_identifier : str
181            The device identifier of the Netbox
182
183        Returns
184        -------
185        str
186            The IP address of the Netbox
187        """
188        ip = device_identifier
189        ip = ip[ip.find('::') + 2:]
190        ip = ip[:ip.find ('::')]
191        return ip

Returns the IP address of the Netbox using the device identifier

Parameters
  • device_identifier (str): The device identifier of the Netbox
Returns
  • str: The IP address of the Netbox
@staticmethod
def discover( max_num_remote_cards: int = 50, max_visa_string_len: int = 256, max_idn_string_len: int = 256, timeout_ms: int = 5000) -> dict[list[str]]:
193    @staticmethod
194    def discover(max_num_remote_cards : int = 50, max_visa_string_len : int = 256, max_idn_string_len : int = 256, timeout_ms : int = 5000) -> dict[list[str]]:
195        """
196        Do a discovery of the cards connected through a network
197
198        Parameters
199        ----------
200        max_num_remote_cards : int = 50
201            the maximum number of remote cards that can be discovered
202        max_visa_string_len : int = 256
203            the maximum length of the VISA string
204        max_idn_string_len : int = 256
205            the maximum length of the IDN string
206        timeout_ms : int = 5000
207            the timeout in milliseconds for the discovery process
208
209        Returns
210        -------
211        CardStack
212            a stack object with all the discovered cards
213        
214        Raises
215        ------
216        SpcmException
217            No Spectrum devices found
218        """
219
220        visa = (spcm_core.c_char_p * max_num_remote_cards)()
221        for i in range(max_num_remote_cards):
222            visa[i] = spcm_core.cast(spcm_core.create_string_buffer(max_visa_string_len), spcm_core.c_char_p)
223        spcm_core.spcm_dwDiscovery (visa, spcm_core.uint32(max_num_remote_cards), spcm_core.uint32(max_visa_string_len), spcm_core.uint32(timeout_ms))
224
225        # ----- check from which manufacturer the devices are -----
226        idn = (spcm_core.c_char_p * max_num_remote_cards)()
227        for i in range(max_num_remote_cards):
228            idn[i] = spcm_core.cast(spcm_core.create_string_buffer(max_idn_string_len), spcm_core.c_char_p)
229        spcm_core.spcm_dwSendIDNRequest (idn, spcm_core.uint32(max_num_remote_cards), spcm_core.uint32(max_idn_string_len))
230
231        # ----- store VISA strings for all discovered cards and open them afterwards -----
232        list_spectrum_devices = {}
233        for (id, visa) in zip(idn, visa):
234            if not id:
235                break
236
237            if id.decode('utf-8').startswith("Spectrum GmbH,"):
238                ip = __class__.id_to_ip(visa.decode("utf-8"))
239                if ip in list_spectrum_devices:
240                    list_spectrum_devices[ip].append(visa.decode("utf-8"))
241                else:
242                    list_spectrum_devices[ip] = [visa.decode("utf-8")]
243        
244        if not list_spectrum_devices:
245            raise SpcmException("No Spectrum devices found")
246        
247        return list_spectrum_devices

Do a discovery of the cards connected through a network

Parameters
  • max_num_remote_cards (int = 50): the maximum number of remote cards that can be discovered
  • max_visa_string_len (int = 256): the maximum length of the VISA string
  • max_idn_string_len (int = 256): the maximum length of the IDN string
  • timeout_ms (int = 5000): the timeout in milliseconds for the discovery process
Returns
  • CardStack: a stack object with all the discovered cards
Raises
  • SpcmException: No Spectrum devices found
class Netbox(spcm.CardStack):
  9class Netbox(CardStack):
 10    """
 11    A hardware class that controls a Netbox device
 12 
 13    Parameters
 14    ----------
 15    netbox_card : Card
 16        a card object that is the main card in the Netbox
 17    netbox_number : int
 18        the index of the netbox card in the list of cards
 19    is_netbox : bool
 20        a boolean that indicates if the card is a Netbox
 21    
 22    """
 23    netbox_card : Card = None
 24    netbox_number : int = -1
 25    is_netbox : bool = False
 26
 27    def __init__(self, card_identifiers : list[str] = [], sync_identifier : str = "", find_sync : bool = False, **kwargs) -> None:
 28        """
 29        Initialize the Netbox object with a list of card identifiers and a sync identifier
 30
 31        Parameters
 32        ----------
 33        card_identifiers : list[str] = []
 34            a list of strings that represent the VISA strings of the cards
 35        sync_identifier : str = ""
 36            a string that represents the VISA string of the sync card
 37        find_sync : bool = False
 38            a boolean that indicates if the sync card should be found automatically
 39        """
 40
 41        super().__init__(card_identifiers, sync_identifier, find_sync, **kwargs)
 42
 43        for id, card in enumerate(self.cards):
 44            netbox_type = card.get_i(SPC_NETBOX_TYPE)
 45            if netbox_type != 0:
 46                self.netbox_card = card
 47                self.netbox_number = id
 48                self.is_netbox = True
 49                break
 50
 51    def __bool__(self) -> bool:
 52        """
 53        Checks if the Netbox is connected and returns true if the connection is alive
 54
 55        Returns
 56        -------
 57        bool
 58            True if the Netbox is connected
 59        """
 60
 61        return self.is_netbox
 62    
 63    def __str__(self) -> str:
 64        """
 65        Returns the string representation of the Netbox
 66
 67        Returns
 68        -------
 69        str
 70            The string representation of the Netbox
 71        """
 72
 73        netbox_type = self.type()
 74        netbox_str = "DN{series:x}.{family:x}{speed:x}-{channel:d}".format(**netbox_type)
 75        return f"Netbox: {netbox_str} at {self.ip()} sn {self.sn():05d}"
 76    __repr__ = __str__
 77        
 78    def type(self) -> dict[int, int, int, int]:
 79        """
 80        Returns the type of the Netbox (see register 'SPC_NETBOX_TYPE' in chapter `Netbox` in the manual)
 81
 82        Returns
 83        -------
 84        dict[int, int, int, int]
 85            A dictionary with the series, family, speed and number of channels of the Netbox
 86        """
 87
 88        netbox_type = self.netbox_card.get_i(SPC_NETBOX_TYPE)
 89        netbox_series = (netbox_type & NETBOX_SERIES_MASK) >> 24
 90        netbox_family = (netbox_type & NETBOX_FAMILY_MASK) >> 16
 91        netbox_speed = (netbox_type & NETBOX_SPEED_MASK) >> 8
 92        netbox_channel = (netbox_type & NETBOX_CHANNEL_MASK)
 93        return {"series" : netbox_series, "family" : netbox_family, "speed" : netbox_speed, "channel" : netbox_channel}
 94
 95    def ip(self) -> str:
 96        """
 97        Returns the IP address of the Netbox using the device identifier of the netbox_card
 98
 99        Returns
100        -------
101        str
102            The IP address of the Netbox
103        """
104        
105        return self.id_to_ip(self.netbox_card.device_identifier)
106
107    def sn(self) -> int:
108        """
109        Returns the serial number of the Netbox (see register 'SPC_NETBOX_SERIALNO' in chapter `Netbox` in the manual)
110
111        Returns
112        -------
113        int
114            The serial number of the Netbox
115        """
116
117        return self.netbox_card.get_i(SPC_NETBOX_SERIALNO)
118    
119    def production_date(self) -> int:
120        """
121        Returns the production date of the Netbox (see register 'SPC_NETBOX_PRODUCTIONDATE' in chapter `Netbox` in the manual)
122
123        Returns
124        -------
125        int
126            The production date of the Netbox
127        """
128
129        return self.netbox_card.get_i(SPC_NETBOX_PRODUCTIONDATE)
130    
131    def hw_version(self) -> int:
132        """
133        Returns the hardware version of the Netbox (see register 'SPC_NETBOX_HWVERSION' in chapter `Netbox` in the manual)
134
135        Returns
136        -------
137        int
138            The hardware version of the Netbox
139        """
140
141        return self.netbox_card.get_i(SPC_NETBOX_HWVERSION)
142    
143    def sw_version(self) -> int:
144        """
145        Returns the software version of the Netbox (see register 'SPC_NETBOX_SWVERSION' in chapter `Netbox` in the manual)
146
147        Returns
148        -------
149        int
150            The software version of the Netbox
151        """
152
153        return self.netbox_card.get_i(SPC_NETBOX_SWVERSION)
154
155    def features(self) -> int:
156        """
157        Returns the features of the Netbox (see register 'SPC_NETBOX_FEATURES' in chapter `Netbox` in the manual)
158
159        Returns
160        -------
161        int
162            The features of the Netbox
163        """
164        
165        return self.netbox_card.get_i(SPC_NETBOX_FEATURES)
166    
167    def custom(self) -> int:
168        """
169        Returns the custom code of the Netbox (see register 'SPC_NETBOX_CUSTOM' in chapter `Netbox` in the manual)
170
171        Returns
172        -------
173        int
174            The custom of the Netbox
175        """
176        return self.netbox_card.get_i(SPC_NETBOX_CUSTOM)
177
178    # def wake_on_lan(self, mac : int):
179    #     """
180    #     Set the wake on lan for the Netbox (see register 'SPC_NETBOX_WAKEONLAN' in chapter `Netbox` in the manual)
181
182    #     Parameters
183    #     ----------
184    #     mac : int
185    #         The mac addresse of the Netbox to wake on lan
186    #     """
187    #     self.netbox_card.set_i(SPC_NETBOX_WAKEONLAN, mac)
188
189    def mac_address(self) -> int:
190        """
191        Returns the mac address of the Netbox (see register 'SPC_NETBOX_MACADDRESS' in chapter `Netbox` in the manual)
192
193        Returns
194        -------
195        int
196            The mac address of the Netbox
197        """
198        return self.netbox_card.get_i(SPC_NETBOX_MACADDRESS)
199    
200    def temperature(self) -> int:
201        """
202        Returns the temperature of the Netbox (see register 'SPC_NETBOX_TEMPERATURE' in chapter `Netbox` in the manual)
203
204        Returns
205        -------
206        int
207            The temperature of the Netbox
208        """
209        return self.netbox_card.get_i(SPC_NETBOX_TEMPERATURE)
210    
211    def shutdown(self):
212        """
213        Shutdown the Netbox (see register 'SPC_NETBOX_SHUTDOWN' in chapter `Netbox` in the manual)
214        """
215        self.netbox_card.set_i(SPC_NETBOX_SHUTDOWN, 0)
216
217    def restart(self):
218        """
219        Restart the Netbox (see register 'SPC_NETBOX_RESTART' in chapter `Netbox` in the manual)
220        """
221        self.netbox_card.set_i(SPC_NETBOX_RESTART, 0)
222    
223    def fan_speed(self, id : int) -> int:
224        """
225        Returns the fan speed of the Netbox (see register 'SPC_NETBOX_FANSPEED' in chapter `Netbox` in the manual)
226
227        Returns
228        -------
229        int
230            The fan speed of the Netbox
231        """
232        return self.netbox_card.get_i(SPC_NETBOX_FANSPEED0 + id)

A hardware class that controls a Netbox device

Parameters
  • netbox_card (Card): a card object that is the main card in the Netbox
  • netbox_number (int): the index of the netbox card in the list of cards
  • is_netbox (bool): a boolean that indicates if the card is a Netbox
Netbox( card_identifiers: list[str] = [], sync_identifier: str = '', find_sync: bool = False, **kwargs)
27    def __init__(self, card_identifiers : list[str] = [], sync_identifier : str = "", find_sync : bool = False, **kwargs) -> None:
28        """
29        Initialize the Netbox object with a list of card identifiers and a sync identifier
30
31        Parameters
32        ----------
33        card_identifiers : list[str] = []
34            a list of strings that represent the VISA strings of the cards
35        sync_identifier : str = ""
36            a string that represents the VISA string of the sync card
37        find_sync : bool = False
38            a boolean that indicates if the sync card should be found automatically
39        """
40
41        super().__init__(card_identifiers, sync_identifier, find_sync, **kwargs)
42
43        for id, card in enumerate(self.cards):
44            netbox_type = card.get_i(SPC_NETBOX_TYPE)
45            if netbox_type != 0:
46                self.netbox_card = card
47                self.netbox_number = id
48                self.is_netbox = True
49                break

Initialize the Netbox object with a list of card identifiers and a sync identifier

Parameters
  • card_identifiers (list[str] = []): a list of strings that represent the VISA strings of the cards
  • sync_identifier (str = ""): a string that represents the VISA string of the sync card
  • find_sync (bool = False): a boolean that indicates if the sync card should be found automatically
netbox_card: Card = None
netbox_number: int = -1
is_netbox: bool = False
def type(self) -> dict[int, int, int, int]:
78    def type(self) -> dict[int, int, int, int]:
79        """
80        Returns the type of the Netbox (see register 'SPC_NETBOX_TYPE' in chapter `Netbox` in the manual)
81
82        Returns
83        -------
84        dict[int, int, int, int]
85            A dictionary with the series, family, speed and number of channels of the Netbox
86        """
87
88        netbox_type = self.netbox_card.get_i(SPC_NETBOX_TYPE)
89        netbox_series = (netbox_type & NETBOX_SERIES_MASK) >> 24
90        netbox_family = (netbox_type & NETBOX_FAMILY_MASK) >> 16
91        netbox_speed = (netbox_type & NETBOX_SPEED_MASK) >> 8
92        netbox_channel = (netbox_type & NETBOX_CHANNEL_MASK)
93        return {"series" : netbox_series, "family" : netbox_family, "speed" : netbox_speed, "channel" : netbox_channel}

Returns the type of the Netbox (see register 'SPC_NETBOX_TYPE' in chapter Netbox in the manual)

Returns
  • dict[int, int, int, int]: A dictionary with the series, family, speed and number of channels of the Netbox
def ip(self) -> str:
 95    def ip(self) -> str:
 96        """
 97        Returns the IP address of the Netbox using the device identifier of the netbox_card
 98
 99        Returns
100        -------
101        str
102            The IP address of the Netbox
103        """
104        
105        return self.id_to_ip(self.netbox_card.device_identifier)

Returns the IP address of the Netbox using the device identifier of the netbox_card

Returns
  • str: The IP address of the Netbox
def sn(self) -> int:
107    def sn(self) -> int:
108        """
109        Returns the serial number of the Netbox (see register 'SPC_NETBOX_SERIALNO' in chapter `Netbox` in the manual)
110
111        Returns
112        -------
113        int
114            The serial number of the Netbox
115        """
116
117        return self.netbox_card.get_i(SPC_NETBOX_SERIALNO)

Returns the serial number of the Netbox (see register 'SPC_NETBOX_SERIALNO' in chapter Netbox in the manual)

Returns
  • int: The serial number of the Netbox
def production_date(self) -> int:
119    def production_date(self) -> int:
120        """
121        Returns the production date of the Netbox (see register 'SPC_NETBOX_PRODUCTIONDATE' in chapter `Netbox` in the manual)
122
123        Returns
124        -------
125        int
126            The production date of the Netbox
127        """
128
129        return self.netbox_card.get_i(SPC_NETBOX_PRODUCTIONDATE)

Returns the production date of the Netbox (see register 'SPC_NETBOX_PRODUCTIONDATE' in chapter Netbox in the manual)

Returns
  • int: The production date of the Netbox
def hw_version(self) -> int:
131    def hw_version(self) -> int:
132        """
133        Returns the hardware version of the Netbox (see register 'SPC_NETBOX_HWVERSION' in chapter `Netbox` in the manual)
134
135        Returns
136        -------
137        int
138            The hardware version of the Netbox
139        """
140
141        return self.netbox_card.get_i(SPC_NETBOX_HWVERSION)

Returns the hardware version of the Netbox (see register 'SPC_NETBOX_HWVERSION' in chapter Netbox in the manual)

Returns
  • int: The hardware version of the Netbox
def sw_version(self) -> int:
143    def sw_version(self) -> int:
144        """
145        Returns the software version of the Netbox (see register 'SPC_NETBOX_SWVERSION' in chapter `Netbox` in the manual)
146
147        Returns
148        -------
149        int
150            The software version of the Netbox
151        """
152
153        return self.netbox_card.get_i(SPC_NETBOX_SWVERSION)

Returns the software version of the Netbox (see register 'SPC_NETBOX_SWVERSION' in chapter Netbox in the manual)

Returns
  • int: The software version of the Netbox
def features(self) -> int:
155    def features(self) -> int:
156        """
157        Returns the features of the Netbox (see register 'SPC_NETBOX_FEATURES' in chapter `Netbox` in the manual)
158
159        Returns
160        -------
161        int
162            The features of the Netbox
163        """
164        
165        return self.netbox_card.get_i(SPC_NETBOX_FEATURES)

Returns the features of the Netbox (see register 'SPC_NETBOX_FEATURES' in chapter Netbox in the manual)

Returns
  • int: The features of the Netbox
def custom(self) -> int:
167    def custom(self) -> int:
168        """
169        Returns the custom code of the Netbox (see register 'SPC_NETBOX_CUSTOM' in chapter `Netbox` in the manual)
170
171        Returns
172        -------
173        int
174            The custom of the Netbox
175        """
176        return self.netbox_card.get_i(SPC_NETBOX_CUSTOM)

Returns the custom code of the Netbox (see register 'SPC_NETBOX_CUSTOM' in chapter Netbox in the manual)

Returns
  • int: The custom of the Netbox
def mac_address(self) -> int:
189    def mac_address(self) -> int:
190        """
191        Returns the mac address of the Netbox (see register 'SPC_NETBOX_MACADDRESS' in chapter `Netbox` in the manual)
192
193        Returns
194        -------
195        int
196            The mac address of the Netbox
197        """
198        return self.netbox_card.get_i(SPC_NETBOX_MACADDRESS)

Returns the mac address of the Netbox (see register 'SPC_NETBOX_MACADDRESS' in chapter Netbox in the manual)

Returns
  • int: The mac address of the Netbox
def temperature(self) -> int:
200    def temperature(self) -> int:
201        """
202        Returns the temperature of the Netbox (see register 'SPC_NETBOX_TEMPERATURE' in chapter `Netbox` in the manual)
203
204        Returns
205        -------
206        int
207            The temperature of the Netbox
208        """
209        return self.netbox_card.get_i(SPC_NETBOX_TEMPERATURE)

Returns the temperature of the Netbox (see register 'SPC_NETBOX_TEMPERATURE' in chapter Netbox in the manual)

Returns
  • int: The temperature of the Netbox
def shutdown(self):
211    def shutdown(self):
212        """
213        Shutdown the Netbox (see register 'SPC_NETBOX_SHUTDOWN' in chapter `Netbox` in the manual)
214        """
215        self.netbox_card.set_i(SPC_NETBOX_SHUTDOWN, 0)

Shutdown the Netbox (see register 'SPC_NETBOX_SHUTDOWN' in chapter Netbox in the manual)

def restart(self):
217    def restart(self):
218        """
219        Restart the Netbox (see register 'SPC_NETBOX_RESTART' in chapter `Netbox` in the manual)
220        """
221        self.netbox_card.set_i(SPC_NETBOX_RESTART, 0)

Restart the Netbox (see register 'SPC_NETBOX_RESTART' in chapter Netbox in the manual)

def fan_speed(self, id: int) -> int:
223    def fan_speed(self, id : int) -> int:
224        """
225        Returns the fan speed of the Netbox (see register 'SPC_NETBOX_FANSPEED' in chapter `Netbox` in the manual)
226
227        Returns
228        -------
229        int
230            The fan speed of the Netbox
231        """
232        return self.netbox_card.get_i(SPC_NETBOX_FANSPEED0 + id)

Returns the fan speed of the Netbox (see register 'SPC_NETBOX_FANSPEED' in chapter Netbox in the manual)

Returns
  • int: The fan speed of the Netbox
class CardFunctionality:
 6class CardFunctionality:
 7    """
 8    A prototype class for card specific functionality that needs it's own namespace
 9    """
10    card : Card
11    function_type = 0
12
13    def __init__(self, card : Card, *args, **kwargs) -> None:
14        """
15        Takes a Card object that is used by the functionality
16
17        Parameters
18        ----------
19        card : Card
20            a Card object on which the functionality works
21        """
22        self.card = card
23        self.function_type = self.card.function_type()
24    
25    
26    # Check if a card was found
27    def __bool__(self) -> bool:
28        """
29        Check for a connection to the active card
30    
31        Returns
32        -------
33        bool
34            True for an active connection and false otherwise
35        
36        """
37        
38        return bool(self.card)

A prototype class for card specific functionality that needs it's own namespace

CardFunctionality(card: Card, *args, **kwargs)
13    def __init__(self, card : Card, *args, **kwargs) -> None:
14        """
15        Takes a Card object that is used by the functionality
16
17        Parameters
18        ----------
19        card : Card
20            a Card object on which the functionality works
21        """
22        self.card = card
23        self.function_type = self.card.function_type()

Takes a Card object that is used by the functionality

Parameters
  • card (Card): a Card object on which the functionality works
card: Card
function_type = 0
class Channels:
522class Channels:
523    """
524    a higher-level abstraction of the CardFunctionality class to implement the Card's channel settings
525    """
526    
527    cards : list[Card] = []
528    channels : list[Channel] = []
529    num_channels : list[int] = []
530    _special_clock : list[bool] = []
531
532    def __init__(self, card : Card = None, card_enable : int = None, stack : CardStack = None, stack_enable : list[int] = None) -> None:
533        """
534        Constructor of the Channels class
535
536        Parameters
537        ----------
538        card : Card = None
539            The card to be used
540        card_enable : int = None
541            The bitmask to enable specific channels
542        stack : CardStack = None
543            The card stack to be used
544        stack_enable : list[int] = None
545            The list of bitmasks to enable specific channels
546
547        Raises
548        ------
549        SpcmException
550            No card or card stack provided
551        """
552
553        self.cards = []
554        self.channels = []
555        self.num_channels = []
556        self._special_clock = []
557        if card is not None:
558            self.cards.append(card)
559            if card_enable is not None:
560                self.channels_enable(enable_list=[card_enable])
561            else:
562                self.channels_enable(enable_all=True)
563        elif stack is not None:
564            self.cards = stack.cards
565            if stack_enable is not None:
566                self.channels_enable(enable_list=stack_enable)
567            else:
568                self.channels_enable(enable_all=True)
569        else:
570            raise SpcmException(text="No card or card stack provided")
571
572    def __str__(self) -> str:
573        """
574        String representation of the Channels class
575    
576        Returns
577        -------
578        str
579            String representation of the Channels class
580        """
581        
582        return f"Channels()"
583    
584    __repr__ = __str__
585
586    def __iter__(self) -> "Channels":
587        """Define this class as an iterator"""
588        return self
589    
590    def __getitem__(self, index : int) -> Channel:
591        """
592        This method is called to access the channel by index
593
594        Parameters
595        ----------
596        index : int
597            The index of the channel
598        
599        Returns
600        -------
601        Channel
602            the channel at the specific index
603        """
604
605        if index < 0 or index >= len(self.channels):
606            raise IndexError(repr(index))
607        return self.channels[index]
608    
609    _channel_iterator_index = -1
610    def __next__(self) -> Channel:
611        """
612        This method is called when the next element is requested from the iterator
613
614        Returns
615        -------
616        Channel
617            the next available channel
618        
619        Raises
620        ------
621        StopIteration
622        """
623        self._channel_iterator_index += 1
624        if self._channel_iterator_index >= len(self.channels):
625            self._channel_iterator_index = -1
626            raise StopIteration
627        return self.channels[self._channel_iterator_index]
628    
629    def __len__(self) -> int:
630        """Returns the number of channels"""
631        return len(self.channels)
632    
633    def write_setup(self) -> None:
634        """Write the setup to the card"""
635        for card in self.cards:
636            card.write_setup()
637    
638    def channels_enable(self, enable_list : list[int] = None, enable_all : bool = False) -> int:
639        """
640        Enables or disables the channels of all the available cards (see register `SPC_CHENABLE` in the manual)
641    
642        Parameters
643        ----------
644        enable_list : list[int] = None
645            A list of channels bitmasks to be enable or disable specific channels
646        enable_all : bool = False
647            Enable all the channels
648        
649        Returns
650        -------
651        int
652            A list with items that indicate for each card the number of channels that are enabled, or True to enable all channels.
653        """
654
655        self.channels = []
656        self.num_channels = []
657        num_channels = 0
658
659        if enable_all:
660            for card in self.cards:
661                special_clock = card.get_i(SPC_SPECIALCLOCK)
662                self._special_clock.append(special_clock)
663                num_channels = card.num_channels()
664                card.set_i(SPC_CHENABLE, (1 << num_channels) - 1)
665                num_channels = card.get_i(SPC_CHCOUNT)
666                self.num_channels.append(num_channels)
667                for i in range(num_channels):
668                    self.channels.append(Channel(i, i, card, special_clock=special_clock))
669        elif enable_list is not None:
670            for enable, card in zip(enable_list, self.cards):
671                special_clock = card.get_i(SPC_SPECIALCLOCK)
672                self._special_clock.append(special_clock)
673                card.set_i(SPC_CHENABLE, enable)
674                num_channels = card.get_i(SPC_CHCOUNT)
675                self.num_channels.append(num_channels)
676                counter = 0
677                for i in range(len(bin(enable))):
678                    if (enable >> i) & 1:
679                        self.channels.append(Channel(i, counter, card, special_clock=special_clock))
680                        counter += 1
681        return sum(self.num_channels)
682        
683    # def __getattribute__(self, name):
684    #     # print("Calling __getattr__: {}".format(name))
685    #     if hasattr(Channel, name):
686    #         def wrapper(*args, **kw):
687    #             for channel in self.channels:
688    #                 getattr(channel, name)(*args, **kw)
689    #         return wrapper
690    #     else:
691    #         return object.__getattribute__(self, name)
692
693    def enable(self, enable : bool) -> None:
694        """
695        Enables or disables the analog front-end of all channels of the card (see register `SPC_ENABLEOUT` in the manual)
696    
697        Parameters
698        ----------
699        enable : bool
700            Turn-on (True) or off (False) the spezific channel
701        """
702
703        for channel in self.channels:
704            channel.enable(enable)
705    enable_out = enable
706    
707    def path(self, value : int) -> None:
708        """
709        Sets the input path of the analog front-end of all channels of the card (see register `SPC_PATH` in the manual)
710    
711        Parameters
712        ----------
713        value : int
714            The input path of the specific channel
715        """
716
717        for channel in self.channels:
718            channel.path(value)
719    
720    def amp(self, value : int) -> None:
721        """
722        Sets the output/input range (amplitude) of the analog front-end of all channels of the card in mV (see register `SPC_AMP` in the manual)
723    
724        Parameters
725        ----------
726        value : int
727            The output range (amplitude) of all channels in millivolts
728        """
729
730        for channel in self.channels:
731            channel.amp(value)
732    
733    def offset(self, value : int) -> None:
734        """
735        Sets the offset of the analog front-end of all channels of the card in mV (see register `SPC_OFFSET` in the manual)
736    
737        Parameters
738        ----------
739        value : int
740            The offset of all channels in millivolts
741        """
742
743        for channel in self.channels:
744            channel.offset(value)
745
746    def termination(self, value : int) -> None:
747        """
748        Sets the termination of the analog front-end of all channels of the card (see register `SPC_50OHM` in the manual)
749    
750        Parameters
751        ----------
752        value : int
753            The termination of all channels
754        """
755
756        for channel in self.channels:
757            channel.termination(value)
758    
759    def digital_termination(self, word_id : int, value : int) -> None:
760        """
761        Sets the termination of the digital front-end of a specific word (16 channels / bits) of channels of the card (see register `SPC_110OHM0` in the manual)
762    
763        Parameters
764        ----------
765        word_id : int
766            The ID of the word of channels (e.g. 0 = D15 - D0, 1 = D31 - D16)
767        value : bool | int
768            The termination of all channels (0 = high-Z, 1 = 110 Ohm)
769        """
770
771        for card in self.cards:
772            card.set_i(SPC_110OHM0 + word_id * (SPC_110OHM1 - SPC_110OHM0), int(value))
773    
774    def coupling(self, value : int) -> None:
775        """
776        Sets the coupling of the analog front-end of all channels of the card (see register `SPC_ACDC` in the manual)
777    
778        Parameters
779        ----------
780        value : int
781            The coupling of all channels
782        """
783
784        for channel in self.channels:
785            channel.coupling(value)
786    
787    def coupling_offset_compensation(self, value : int) -> None:
788        """
789        Sets the coupling offset compensation of the analog front-end of all channels of the card (see register `SPC_ACDC_OFFS_COMPENSATION` in the manual)
790    
791        Parameters
792        ----------
793        value : int
794            The coupling offset compensation of all channels
795        """
796
797        for channel in self.channels:
798            channel.coupling_offset_compensation(value)
799    
800    def filter(self, value : int) -> None:
801        """
802        Sets the filter of the analog front-end of all channels of the card (see register `SPC_FILTER` in the manual)
803    
804        Parameters
805        ----------
806        value : int
807            The filter of all channels
808        """
809
810        for channel in self.channels:
811            channel.filter(value)
812    
813    def stop_level(self, value : int) -> None:
814        """
815        Usually the used outputs of the analog generation boards are set to zero level after replay. 
816        This is in most cases adequate. In some cases it can be necessary to hold the last sample,
817        to output the maximum positive level or maximum negative level after replay. The stoplevel will 
818        stay on the defined level until the next output has been made. With this function
819        you can define the behavior after replay (see register `SPC_CH0_STOPLEVEL` in the manual)
820    
821        Parameters
822        ----------
823        value : int
824            The wanted stop behaviour:
825        """
826
827        for channel in self.channels:
828            channel.stop_level(value)
829
830    def custom_stop(self, value : int) -> None:
831        """
832        Allows to define a 16bit wide custom level per channel for the analog output to enter in pauses. The sample format is 
833        exactly the same as during replay, as described in the „sample format“ section.
834        When synchronous digital bits are replayed along, the custom level must include these as well and therefore allows to 
835        set a custom level for each multi-purpose line separately. (see register `SPC_CH0_CUSTOM_STOP` in the manual)
836    
837        Parameters
838        ----------
839        value : int
840            The custom stop value
841        """
842    
843        for channel in self.channels:
844            channel.custom_stop(value)
845    
846    def output_load(self, value : pint.Quantity) -> None:
847        """
848        Sets the electrical load of the user system connect the channel of the card. This is important for the correct
849        calculation of the output power. Typically, the load would be 50 Ohms, but it can be different.
850
851        Parameters
852        ----------
853        value : pint.Quantity
854            The electrical load connected by the user to the specific channel
855        """
856        for channel in self.channels:
857            channel.output_load(value)
858    
859    def ch_mask(self) -> int:
860        """
861        Gets mask for the "or"- or "and"-mask
862
863        Returns
864        -------
865        int
866            The mask for the "or"- or "and"-mask
867        """
868
869        return sum([channel.ch_mask() for channel in self.channels])

a higher-level abstraction of the CardFunctionality class to implement the Card's channel settings

Channels( card: Card = None, card_enable: int = None, stack: CardStack = None, stack_enable: list[int] = None)
532    def __init__(self, card : Card = None, card_enable : int = None, stack : CardStack = None, stack_enable : list[int] = None) -> None:
533        """
534        Constructor of the Channels class
535
536        Parameters
537        ----------
538        card : Card = None
539            The card to be used
540        card_enable : int = None
541            The bitmask to enable specific channels
542        stack : CardStack = None
543            The card stack to be used
544        stack_enable : list[int] = None
545            The list of bitmasks to enable specific channels
546
547        Raises
548        ------
549        SpcmException
550            No card or card stack provided
551        """
552
553        self.cards = []
554        self.channels = []
555        self.num_channels = []
556        self._special_clock = []
557        if card is not None:
558            self.cards.append(card)
559            if card_enable is not None:
560                self.channels_enable(enable_list=[card_enable])
561            else:
562                self.channels_enable(enable_all=True)
563        elif stack is not None:
564            self.cards = stack.cards
565            if stack_enable is not None:
566                self.channels_enable(enable_list=stack_enable)
567            else:
568                self.channels_enable(enable_all=True)
569        else:
570            raise SpcmException(text="No card or card stack provided")

Constructor of the Channels class

Parameters
  • card (Card = None): The card to be used
  • card_enable (int = None): The bitmask to enable specific channels
  • stack (CardStack = None): The card stack to be used
  • stack_enable (list[int] = None): The list of bitmasks to enable specific channels
Raises
  • SpcmException: No card or card stack provided
cards: list[Card] = []
channels: list[Channel] = []
num_channels: list[int] = []
def write_setup(self) -> None:
633    def write_setup(self) -> None:
634        """Write the setup to the card"""
635        for card in self.cards:
636            card.write_setup()

Write the setup to the card

def channels_enable(self, enable_list: list[int] = None, enable_all: bool = False) -> int:
638    def channels_enable(self, enable_list : list[int] = None, enable_all : bool = False) -> int:
639        """
640        Enables or disables the channels of all the available cards (see register `SPC_CHENABLE` in the manual)
641    
642        Parameters
643        ----------
644        enable_list : list[int] = None
645            A list of channels bitmasks to be enable or disable specific channels
646        enable_all : bool = False
647            Enable all the channels
648        
649        Returns
650        -------
651        int
652            A list with items that indicate for each card the number of channels that are enabled, or True to enable all channels.
653        """
654
655        self.channels = []
656        self.num_channels = []
657        num_channels = 0
658
659        if enable_all:
660            for card in self.cards:
661                special_clock = card.get_i(SPC_SPECIALCLOCK)
662                self._special_clock.append(special_clock)
663                num_channels = card.num_channels()
664                card.set_i(SPC_CHENABLE, (1 << num_channels) - 1)
665                num_channels = card.get_i(SPC_CHCOUNT)
666                self.num_channels.append(num_channels)
667                for i in range(num_channels):
668                    self.channels.append(Channel(i, i, card, special_clock=special_clock))
669        elif enable_list is not None:
670            for enable, card in zip(enable_list, self.cards):
671                special_clock = card.get_i(SPC_SPECIALCLOCK)
672                self._special_clock.append(special_clock)
673                card.set_i(SPC_CHENABLE, enable)
674                num_channels = card.get_i(SPC_CHCOUNT)
675                self.num_channels.append(num_channels)
676                counter = 0
677                for i in range(len(bin(enable))):
678                    if (enable >> i) & 1:
679                        self.channels.append(Channel(i, counter, card, special_clock=special_clock))
680                        counter += 1
681        return sum(self.num_channels)

Enables or disables the channels of all the available cards (see register SPC_CHENABLE in the manual)

Parameters
  • enable_list (list[int] = None): A list of channels bitmasks to be enable or disable specific channels
  • enable_all (bool = False): Enable all the channels
Returns
  • int: A list with items that indicate for each card the number of channels that are enabled, or True to enable all channels.
def enable(self, enable: bool) -> None:
693    def enable(self, enable : bool) -> None:
694        """
695        Enables or disables the analog front-end of all channels of the card (see register `SPC_ENABLEOUT` in the manual)
696    
697        Parameters
698        ----------
699        enable : bool
700            Turn-on (True) or off (False) the spezific channel
701        """
702
703        for channel in self.channels:
704            channel.enable(enable)

Enables or disables the analog front-end of all channels of the card (see register SPC_ENABLEOUT in the manual)

Parameters
  • enable (bool): Turn-on (True) or off (False) the spezific channel
def enable_out(self, enable: bool) -> None:
693    def enable(self, enable : bool) -> None:
694        """
695        Enables or disables the analog front-end of all channels of the card (see register `SPC_ENABLEOUT` in the manual)
696    
697        Parameters
698        ----------
699        enable : bool
700            Turn-on (True) or off (False) the spezific channel
701        """
702
703        for channel in self.channels:
704            channel.enable(enable)

Enables or disables the analog front-end of all channels of the card (see register SPC_ENABLEOUT in the manual)

Parameters
  • enable (bool): Turn-on (True) or off (False) the spezific channel
def path(self, value: int) -> None:
707    def path(self, value : int) -> None:
708        """
709        Sets the input path of the analog front-end of all channels of the card (see register `SPC_PATH` in the manual)
710    
711        Parameters
712        ----------
713        value : int
714            The input path of the specific channel
715        """
716
717        for channel in self.channels:
718            channel.path(value)

Sets the input path of the analog front-end of all channels of the card (see register SPC_PATH in the manual)

Parameters
  • value (int): The input path of the specific channel
def amp(self, value: int) -> None:
720    def amp(self, value : int) -> None:
721        """
722        Sets the output/input range (amplitude) of the analog front-end of all channels of the card in mV (see register `SPC_AMP` in the manual)
723    
724        Parameters
725        ----------
726        value : int
727            The output range (amplitude) of all channels in millivolts
728        """
729
730        for channel in self.channels:
731            channel.amp(value)

Sets the output/input range (amplitude) of the analog front-end of all channels of the card in mV (see register SPC_AMP in the manual)

Parameters
  • value (int): The output range (amplitude) of all channels in millivolts
def offset(self, value: int) -> None:
733    def offset(self, value : int) -> None:
734        """
735        Sets the offset of the analog front-end of all channels of the card in mV (see register `SPC_OFFSET` in the manual)
736    
737        Parameters
738        ----------
739        value : int
740            The offset of all channels in millivolts
741        """
742
743        for channel in self.channels:
744            channel.offset(value)

Sets the offset of the analog front-end of all channels of the card in mV (see register SPC_OFFSET in the manual)

Parameters
  • value (int): The offset of all channels in millivolts
def termination(self, value: int) -> None:
746    def termination(self, value : int) -> None:
747        """
748        Sets the termination of the analog front-end of all channels of the card (see register `SPC_50OHM` in the manual)
749    
750        Parameters
751        ----------
752        value : int
753            The termination of all channels
754        """
755
756        for channel in self.channels:
757            channel.termination(value)

Sets the termination of the analog front-end of all channels of the card (see register SPC_50OHM in the manual)

Parameters
  • value (int): The termination of all channels
def digital_termination(self, word_id: int, value: int) -> None:
759    def digital_termination(self, word_id : int, value : int) -> None:
760        """
761        Sets the termination of the digital front-end of a specific word (16 channels / bits) of channels of the card (see register `SPC_110OHM0` in the manual)
762    
763        Parameters
764        ----------
765        word_id : int
766            The ID of the word of channels (e.g. 0 = D15 - D0, 1 = D31 - D16)
767        value : bool | int
768            The termination of all channels (0 = high-Z, 1 = 110 Ohm)
769        """
770
771        for card in self.cards:
772            card.set_i(SPC_110OHM0 + word_id * (SPC_110OHM1 - SPC_110OHM0), int(value))

Sets the termination of the digital front-end of a specific word (16 channels / bits) of channels of the card (see register SPC_110OHM0 in the manual)

Parameters
  • word_id (int): The ID of the word of channels (e.g. 0 = D15 - D0, 1 = D31 - D16)
  • value (bool | int): The termination of all channels (0 = high-Z, 1 = 110 Ohm)
def coupling(self, value: int) -> None:
774    def coupling(self, value : int) -> None:
775        """
776        Sets the coupling of the analog front-end of all channels of the card (see register `SPC_ACDC` in the manual)
777    
778        Parameters
779        ----------
780        value : int
781            The coupling of all channels
782        """
783
784        for channel in self.channels:
785            channel.coupling(value)

Sets the coupling of the analog front-end of all channels of the card (see register SPC_ACDC in the manual)

Parameters
  • value (int): The coupling of all channels
def coupling_offset_compensation(self, value: int) -> None:
787    def coupling_offset_compensation(self, value : int) -> None:
788        """
789        Sets the coupling offset compensation of the analog front-end of all channels of the card (see register `SPC_ACDC_OFFS_COMPENSATION` in the manual)
790    
791        Parameters
792        ----------
793        value : int
794            The coupling offset compensation of all channels
795        """
796
797        for channel in self.channels:
798            channel.coupling_offset_compensation(value)

Sets the coupling offset compensation of the analog front-end of all channels of the card (see register SPC_ACDC_OFFS_COMPENSATION in the manual)

Parameters
  • value (int): The coupling offset compensation of all channels
def filter(self, value: int) -> None:
800    def filter(self, value : int) -> None:
801        """
802        Sets the filter of the analog front-end of all channels of the card (see register `SPC_FILTER` in the manual)
803    
804        Parameters
805        ----------
806        value : int
807            The filter of all channels
808        """
809
810        for channel in self.channels:
811            channel.filter(value)

Sets the filter of the analog front-end of all channels of the card (see register SPC_FILTER in the manual)

Parameters
  • value (int): The filter of all channels
def stop_level(self, value: int) -> None:
813    def stop_level(self, value : int) -> None:
814        """
815        Usually the used outputs of the analog generation boards are set to zero level after replay. 
816        This is in most cases adequate. In some cases it can be necessary to hold the last sample,
817        to output the maximum positive level or maximum negative level after replay. The stoplevel will 
818        stay on the defined level until the next output has been made. With this function
819        you can define the behavior after replay (see register `SPC_CH0_STOPLEVEL` in the manual)
820    
821        Parameters
822        ----------
823        value : int
824            The wanted stop behaviour:
825        """
826
827        for channel in self.channels:
828            channel.stop_level(value)

Usually the used outputs of the analog generation boards are set to zero level after replay. This is in most cases adequate. In some cases it can be necessary to hold the last sample, to output the maximum positive level or maximum negative level after replay. The stoplevel will stay on the defined level until the next output has been made. With this function you can define the behavior after replay (see register SPC_CH0_STOPLEVEL in the manual)

Parameters
  • value (int): The wanted stop behaviour:
def custom_stop(self, value: int) -> None:
830    def custom_stop(self, value : int) -> None:
831        """
832        Allows to define a 16bit wide custom level per channel for the analog output to enter in pauses. The sample format is 
833        exactly the same as during replay, as described in the „sample format“ section.
834        When synchronous digital bits are replayed along, the custom level must include these as well and therefore allows to 
835        set a custom level for each multi-purpose line separately. (see register `SPC_CH0_CUSTOM_STOP` in the manual)
836    
837        Parameters
838        ----------
839        value : int
840            The custom stop value
841        """
842    
843        for channel in self.channels:
844            channel.custom_stop(value)

Allows to define a 16bit wide custom level per channel for the analog output to enter in pauses. The sample format is exactly the same as during replay, as described in the „sample format“ section. When synchronous digital bits are replayed along, the custom level must include these as well and therefore allows to set a custom level for each multi-purpose line separately. (see register SPC_CH0_CUSTOM_STOP in the manual)

Parameters
  • value (int): The custom stop value
def output_load(self, value: pint.registry.Quantity) -> None:
846    def output_load(self, value : pint.Quantity) -> None:
847        """
848        Sets the electrical load of the user system connect the channel of the card. This is important for the correct
849        calculation of the output power. Typically, the load would be 50 Ohms, but it can be different.
850
851        Parameters
852        ----------
853        value : pint.Quantity
854            The electrical load connected by the user to the specific channel
855        """
856        for channel in self.channels:
857            channel.output_load(value)

Sets the electrical load of the user system connect the channel of the card. This is important for the correct calculation of the output power. Typically, the load would be 50 Ohms, but it can be different.

Parameters
  • value (pint.Quantity): The electrical load connected by the user to the specific channel
def ch_mask(self) -> int:
859    def ch_mask(self) -> int:
860        """
861        Gets mask for the "or"- or "and"-mask
862
863        Returns
864        -------
865        int
866            The mask for the "or"- or "and"-mask
867        """
868
869        return sum([channel.ch_mask() for channel in self.channels])

Gets mask for the "or"- or "and"-mask

Returns
  • int: The mask for the "or"- or "and"-mask
class Channel:
 18class Channel:
 19    """A class to represent a channel of a card only used inside the Channels class in the list of channels"""
 20
 21    card : Card = None
 22    index : int = 0
 23    data_index : int = 0
 24
 25    _conversion_amp : pint.Quantity = None
 26    _conversion_clock : float = 1.0
 27    _conversion_offset : pint.Quantity = None
 28    _output_load : pint.Quantity = None
 29    _series_impedance : pint.Quantity = None
 30
 31    def __init__(self, index : int, data_index : int, card : Card, special_clock : bool = False) -> None:
 32        """
 33        Constructor of the Channel class
 34    
 35        Parameters
 36        ----------
 37        index : int
 38            The index of the channel
 39        card : Card
 40            The card of the channel
 41        """
 42
 43        self.card = card
 44        self.index = index
 45        self.data_index = data_index
 46        self._conversion_clock = self.special_clock_adjust(special_clock)
 47        if card.function_type() == SPCM_TYPE_AI or card.function_type() == SPCM_TYPE_AO:
 48            self._conversion_amp = self.amp(return_unit=units.V)
 49            self._conversion_offset = self.offset(return_unit=units.V)
 50        else:
 51            # For digital cards, we do not have a conversion factor, taking 3.3 V as a default
 52            self._conversion_amp = 3.3 * units.V
 53            self._conversion_offset = 0 * units.V
 54        self._output_load = 50 * units.ohm
 55        self._series_impedance = 50 * units.ohm
 56    
 57    def __str__(self) -> str:
 58        """
 59        String representation of the Channel class
 60    
 61        Returns
 62        -------
 63        str
 64            String representation of the Channel class
 65        """
 66        
 67        return f"Channel {self.index}"
 68    
 69    __repr__ = __str__
 70
 71    def __int__(self) -> int:
 72        """
 73        The Channel object acts like an int and returns the index of the channel and can also be used as the index in an array
 74
 75        Returns
 76        -------
 77        int
 78            The index of the channel
 79        """
 80        return self.data_index
 81    __index__ = __int__
 82    
 83    def __add__(self, other):
 84        """
 85        The Channel object again acts like an int and returns the index of the channel plus the other value
 86        
 87        Parameters
 88        ----------
 89        other : int or float
 90            The value to be added to the index of the channel
 91        
 92        Returns
 93        -------
 94        int or float
 95            The index of the channel plus the other value
 96        """
 97        return self.index + other
 98    
 99    def enable(self, enable : bool = None) -> bool:
100        """
101        Enables the analog front-end of the channel of the card (see register `SPC_ENABLEOUT` in the manual)
102    
103        Parameters
104        ----------
105        enable : bool
106            Turn-on (True) or off (False) the spezific channel
107
108        Returns
109        -------
110        bool
111            The enable state of the specific channel
112        """
113
114        if enable is not None:
115            self.card.set_i(SPC_ENABLEOUT0 + (SPC_ENABLEOUT1 - SPC_ENABLEOUT0) * self.index, int(enable))
116        return bool(self.card.get_i(SPC_ENABLEOUT0 + (SPC_ENABLEOUT1 - SPC_ENABLEOUT0) * self.index))
117    enable_out = enable
118    
119    def path(self, value : int = None) -> int:
120        """
121        Sets the input path of the channel of the card (see register `SPC_PATH0` in the manual).
122        To make the read back of the coupling offset compensation setting possible, we also set 
123        the register `SPC_READAIPATH` to the same value.
124    
125        Parameters
126        ----------
127        value : int
128            The input path of the specific channel
129        
130        Returns
131        -------
132        int
133            The input path of the specific channel
134        """
135
136        if value is not None:
137            self.card.set_i(SPC_PATH0 + (SPC_PATH1 - SPC_PATH0) * self.index, value)
138            self.card.set_i(SPC_READAIPATH, value)
139        return self.card.get_i(SPC_PATH0 + (SPC_PATH1 - SPC_PATH0) * self.index)
140    
141    def amp(self, value : int = None, return_unit = None) -> int:
142        """
143        Sets the output/input range (amplitude) of the analog front-end of the channel of the card in mV (see register `SPC_AMP` in the manual)
144    
145        Parameters
146        ----------
147        value : int
148            The output range (amplitude) of the specific channel in millivolts
149        unit : pint.Unit = None
150            The unit of the return value
151            
152        Returns
153        -------
154        int | pint.Quantity
155            The output range (amplitude) of the specific channel in millivolts or the unit specified
156        """
157
158        if value is not None:
159            if isinstance(value, pint.Quantity):
160                value = self.voltage_conversion(value)
161            self._conversion_amp = UnitConversion.force_unit(value, units.mV)
162            value = UnitConversion.convert(value, units.mV, int)
163            self.card.set_i(SPC_AMP0 + (SPC_AMP1 - SPC_AMP0) * self.index, value)
164        value = self.card.get_i(SPC_AMP0 + (SPC_AMP1 - SPC_AMP0) * self.index)
165        value = UnitConversion.to_unit(value * units.mV, return_unit)
166        return value
167
168    def offset(self, value : int = None, return_unit = None) -> int:
169        """
170        Sets the offset of the analog front-end of the channel of the card in % of the full range o rmV (see register `SPC_OFFS0` in the manual)
171        If the value is given and has a unit, then this unit is converted to the unit of the card (mV or %)
172    
173        Parameters
174        ----------
175        value : int | pint.Quantity = None
176            The offset of the specific channel as integer in % or as a Quantity in % or mV
177        unit : pint.Unit = None
178            The unit of the return value
179            
180        Returns
181        -------
182        int | pint.Quantity
183            The offset of the specific channel in % or the unit specified by return_unit
184        """
185
186        # Analog in cards are programmed in percent of the full range and analog output cards in mV (in the M2p, M4i/x and M5i families)
187        card_unit = 1
188        fnc_type = self.card.function_type()
189        if fnc_type == SPCM_TYPE_AI:
190            card_unit = units.percent
191        elif fnc_type == SPCM_TYPE_AO:
192            card_unit = units.mV
193
194        if value is not None:
195            # The user gives a value as a Quantity
196            if isinstance(value, pint.Quantity):
197                if fnc_type == SPCM_TYPE_AO:
198                    # The card expects a value in mV
199                    if value.check('[]'):
200                        # Convert from percent to mV
201                        value = (value * self._conversion_amp).to(card_unit)
202                    else:
203                        value = value.to(card_unit)
204                elif fnc_type == SPCM_TYPE_AI:
205                    # The card expects a value in percent
206                    if value.check('[electric_potential]'):
207                        # Convert from mV to percent
208                        value = (value / self._conversion_amp).to(card_unit)
209                    else:
210                        value = value.to(card_unit)
211            else:
212                # Value is given as a number
213                pass
214
215            value = UnitConversion.convert(value, card_unit, int)
216            self.card.set_i(SPC_OFFS0 + (SPC_OFFS1 - SPC_OFFS0) * self.index, value)
217        
218        return_value = self.card.get_i(SPC_OFFS0 + (SPC_OFFS1 - SPC_OFFS0) * self.index)
219        # Turn the return value into a quantity
220        return_quantity = UnitConversion.to_unit(return_value, return_unit)
221        # Save the conversion offset to be able to convert the data to a quantity with the correct unit
222        self._conversion_offset = UnitConversion.force_unit(return_value, card_unit)
223        return return_quantity
224    
225    def special_clock_adjust(self, special_clock : bool = False) -> float:
226        """
227        Gets the special clock adjust value of the channel (see register `SPC_SPECIALCLOCK_ADJUST0` in the manual)
228        
229        Returns
230        -------
231        float
232            The special clock adjust value of the channel
233        """
234
235        adjust = 1.0
236        if special_clock:
237            # If the card is in special clock mode, we need to adjust the conversion factor
238            adjust = self.card.get_d(SPC_SPECIALCLOCK_ADJUST0 + (SPC_SPECIALCLOCK_ADJUST1 - SPC_SPECIALCLOCK_ADJUST0) * self.index)
239        return adjust
240    
241    def convert_data(self, data : npt.NDArray, return_unit : pint.Unit = units.mV, averages : int = 1) -> npt.NDArray:
242        """
243        Converts the data to the correct unit in units of electrical potential
244        
245        Parameters
246        ----------
247        data : numpy.ndarray
248            The data to be converted
249        return_unit : pint.Unit = units.mV
250            The unit of the return value
251        averages : int = 1
252            The number of averages that have been done to the data and should be taken into account to convert the data
253            
254        Returns
255        -------
256        numpy.ndarray
257            The converted data in units of electrical potential
258        """
259
260        max_value = self.card.max_sample_value() * averages
261        if self._conversion_offset.check('[]'):
262            return_data = (data / max_value - self._conversion_offset) * (self._conversion_amp * self._conversion_clock)
263        else:
264            return_data = (data / max_value) * (self._conversion_amp * self._conversion_clock) - self._conversion_offset
265        return_data = UnitConversion.to_unit(return_data, return_unit)
266        return return_data
267    
268    def reconvert_data(self, data : npt.NDArray) -> npt.NDArray:
269        """
270        Convert data with units back to integer values in units of electrical potential
271        
272        Parameters
273        ----------
274        data : numpy.ndarray
275            The data to be reconverted
276            
277        Returns
278        -------
279        numpy.ndarray
280            The reconverted data as integer in mV
281        """
282
283        if self._conversion_offset.check('[]'):
284            return_data = int((data / (self._conversion_amp * self._conversion_clock) + self._conversion_offset) * self.card.max_sample_value())
285        else:
286            return_data = int(((data + self._conversion_offset) / (self._conversion_amp * self._conversion_clock)) * self.card.max_sample_value())
287        return return_data
288
289    def termination(self, value : int) -> None:
290        """
291        Sets the termination of the analog front-end of the channel of the card (see register `SPC_50OHM0` in the manual)
292    
293        Parameters
294        ----------
295        value : int | bool
296            The termination of the specific channel
297        """
298
299        self.card.set_i(SPC_50OHM0 + (SPC_50OHM1 - SPC_50OHM0) * self.index, int(value))
300
301    def get_termination(self) -> int:
302        """
303        Gets the termination of the analog front-end of the channel of the card (see register `SPC_50OHM0` in the manual)
304            
305        Returns
306        -------
307        int
308            The termination of the specific channel
309        """
310
311        return self.card.get_i(SPC_50OHM0 + (SPC_50OHM1 - SPC_50OHM0) * self.index)
312    
313    def coupling(self, value : int = None) -> int:
314        """
315        Sets the coupling of the analog front-end of the channel of the card (see register `SPC_ACDC0` in the manual)
316    
317        Parameters
318        ----------
319        value : int
320            The coupling of the specific channel
321            
322        Returns
323        -------
324        int
325            The coupling of the specific channel
326        """
327
328        if value is not None:
329            self.card.set_i(SPC_ACDC0 + (SPC_ACDC1 - SPC_ACDC0) * self.index, value)
330        return self.card.get_i(SPC_ACDC0 + (SPC_ACDC1 - SPC_ACDC0) * self.index)
331    
332    def coupling_offset_compensation(self, value : int = None) -> int:
333        """
334        Enables or disables the coupling offset compensation of the analog front-end of the channel of the card (see register `SPC_ACDC_OFFS_COMPENSATION0` in the manual)
335    
336        Parameters
337        ----------
338        value : int
339            Enables the coupling offset compensation of the specific channel
340            
341        Returns
342        -------
343        int
344            return if the coupling offset compensation of the specific channel is enabled ("1") or disabled ("0")
345        """
346
347        if value is not None:
348            self.card.set_i(SPC_ACDC_OFFS_COMPENSATION0 + (SPC_ACDC_OFFS_COMPENSATION1 - SPC_ACDC_OFFS_COMPENSATION0) * self.index, value)
349        return self.card.get_i(SPC_ACDC_OFFS_COMPENSATION0 + (SPC_ACDC_OFFS_COMPENSATION1 - SPC_ACDC_OFFS_COMPENSATION0) * self.index)
350    
351    def filter(self, value : int = None) -> int:
352        """
353        Sets the filter of the analog front-end of the channel of the card (see register `SPC_FILTER0` in the manual)
354    
355        Parameters
356        ----------
357        value : int
358            The filter of the specific channel
359            
360        Returns
361        -------
362        int
363            The filter of the specific channel
364        """
365
366        if value is not None:
367            self.card.set_i(SPC_FILTER0 + (SPC_FILTER1 - SPC_FILTER0) * self.index, value)
368        return self.card.get_i(SPC_FILTER0 + (SPC_FILTER1 - SPC_FILTER0) * self.index)
369    
370    def stop_level(self, value : int = None) -> int:
371        """
372        Usually the used outputs of the analog generation boards are set to zero level after replay. 
373        This is in most cases adequate. In some cases it can be necessary to hold the last sample,
374        to output the maximum positive level or maximum negative level after replay. The stoplevel will 
375        stay on the defined level until the next output has been made. With this function
376        you can define the behavior after replay (see register `SPC_CH0_STOPLEVEL` in the manual)
377    
378        Parameters
379        ----------
380        value : int
381            The wanted stop behaviour
382
383        Returns
384        -------
385        int
386            The stop behaviour of the specific channel
387        """
388
389        if value is not None:
390            self.card.set_i(SPC_CH0_STOPLEVEL + self.index * (SPC_CH1_STOPLEVEL - SPC_CH0_STOPLEVEL), value)
391        return self.card.get_i(SPC_CH0_STOPLEVEL + self.index * (SPC_CH1_STOPLEVEL - SPC_CH0_STOPLEVEL))
392
393    def custom_stop(self, value : int = None) -> int:
394        """
395        Allows to define a 16bit wide custom level per channel for the analog output to enter in pauses. The sample format is 
396        exactly the same as during replay, as described in the „sample format“ section.
397        When synchronous digital bits are replayed along, the custom level must include these as well and therefore allows to 
398        set a custom level for each multi-purpose line separately. (see register `SPC_CH0_CUSTOM_STOP` in the manual)
399    
400        Parameters
401        ----------
402        value : int
403            The custom stop value
404
405        Returns
406        -------
407        int
408            The custom stop value of the specific channel
409        
410        TODO: change this to a specific unit?
411        """
412
413        if value is not None:
414            self.card.set_i(SPC_CH0_CUSTOM_STOP + self.index * (SPC_CH1_CUSTOM_STOP - SPC_CH0_CUSTOM_STOP), value)
415        return self.card.get_i(SPC_CH0_CUSTOM_STOP + self.index * (SPC_CH1_CUSTOM_STOP - SPC_CH0_CUSTOM_STOP))
416    
417    def ch_mask(self) -> int:
418        """
419        Gets mask for the "or"- or "and"-mask
420
421        Returns
422        -------
423        int
424            The mask for the "or"- or "and"-mask
425        """
426
427        return 1 << self.index
428    
429    def output_load(self, value : pint.Quantity = None) -> pint.Quantity:
430        """
431        Sets the electrical load of the user system connect the channel of the card. This is important for the correct
432        calculation of the output power. Typically, the load would be 50 Ohms, but it can be different.
433
434        Parameters
435        ----------
436        value : pint.Quantity
437            The electrical load connected by the user to the specific channel
438
439        Returns
440        -------
441        pint.Quantity
442            The electrical load connected by the user to the specific channel
443        """
444        if value is not None:
445            self._output_load = value
446        return self._output_load
447    
448    def voltage_conversion(self, value : pint.Quantity) -> pint.Quantity:
449        """
450        Convert the voltage that is needed at a certain output load to the voltage setting of the card if the load would be 50 Ohm
451
452        Parameters
453        ----------
454        value : pint.Quantity
455            The voltage that is needed at a certain output load
456
457        Returns
458        -------
459        pint.Quantity
460            The corresponding voltage at an output load of 50 Ohm
461        """
462
463        # The two at the end is because the value expected by the card is defined for a 50 Ohm load
464        if self._output_load == np.inf * units.ohm:
465            return value / 2
466        return value / (self._output_load / (self._output_load + self._series_impedance)) / 2
467
468    def to_amplitude_fraction(self, value) -> float:
469        """
470        Convert the voltage, percentage or power to percentage of the full range of the card
471
472        Parameters
473        ----------
474        value : pint.Quantity | float
475            The voltage that should be outputted at a certain output load
476
477        Returns
478        -------
479        float
480            The corresponding fraction of the full range of the card
481        """
482
483        if isinstance(value, units.Quantity) and value.check("[power]"):
484            # U_pk = U_rms * sqrt(2)
485            value = np.sqrt(2 * value.to('mW') * self._output_load) / self._conversion_amp * 100 * units.percent
486        elif isinstance(value, units.Quantity) and value.check("[electric_potential]"):
487            # value in U_pk
488            value = self.voltage_conversion(value) / self._conversion_amp * 100 * units.percent
489        value = UnitConversion.convert(value, units.fraction, float, rounding=None)
490        return value
491    
492    def from_amplitude_fraction(self, fraction, return_unit : pint.Quantity = None) -> pint.Quantity:
493        """
494        Convert the percentage of the full range to voltage, percentage or power
495
496        Parameters
497        ----------
498        fraction : float
499            The percentage of the full range of the card
500        return_unit : pint.Quantity
501            The unit of the return value
502
503        Returns
504        -------
505        pint.Quantity
506            The corresponding voltage, percentage or power
507        """
508
509        return_value = fraction
510        if isinstance(return_unit, units.Unit) and (1*return_unit).check("[power]"):
511            return_value = (np.power(self._conversion_amp * fraction, 2) / self._output_load / 2).to(return_unit)
512            # U_pk = U_rms * sqrt(2)
513        elif isinstance(return_unit, units.Unit) and (1*return_unit).check("[electric_potential]"):
514            return_value = (self._conversion_amp * fraction / (100 * units.percent)).to(return_unit)
515            # value in U_pk
516            # value = self.voltage_conversion(value) / self._conversion_amp * 100 * units.percent
517        elif isinstance(return_unit, units.Unit) and (1*return_unit).check("[]"):
518            return_value = UnitConversion.force_unit(fraction, return_unit)
519        return return_value

A class to represent a channel of a card only used inside the Channels class in the list of channels

Channel( index: int, data_index: int, card: Card, special_clock: bool = False)
31    def __init__(self, index : int, data_index : int, card : Card, special_clock : bool = False) -> None:
32        """
33        Constructor of the Channel class
34    
35        Parameters
36        ----------
37        index : int
38            The index of the channel
39        card : Card
40            The card of the channel
41        """
42
43        self.card = card
44        self.index = index
45        self.data_index = data_index
46        self._conversion_clock = self.special_clock_adjust(special_clock)
47        if card.function_type() == SPCM_TYPE_AI or card.function_type() == SPCM_TYPE_AO:
48            self._conversion_amp = self.amp(return_unit=units.V)
49            self._conversion_offset = self.offset(return_unit=units.V)
50        else:
51            # For digital cards, we do not have a conversion factor, taking 3.3 V as a default
52            self._conversion_amp = 3.3 * units.V
53            self._conversion_offset = 0 * units.V
54        self._output_load = 50 * units.ohm
55        self._series_impedance = 50 * units.ohm

Constructor of the Channel class

Parameters
  • index (int): The index of the channel
  • card (Card): The card of the channel
card: Card = None
index: int = 0
data_index: int = 0
def enable(self, enable: bool = None) -> bool:
 99    def enable(self, enable : bool = None) -> bool:
100        """
101        Enables the analog front-end of the channel of the card (see register `SPC_ENABLEOUT` in the manual)
102    
103        Parameters
104        ----------
105        enable : bool
106            Turn-on (True) or off (False) the spezific channel
107
108        Returns
109        -------
110        bool
111            The enable state of the specific channel
112        """
113
114        if enable is not None:
115            self.card.set_i(SPC_ENABLEOUT0 + (SPC_ENABLEOUT1 - SPC_ENABLEOUT0) * self.index, int(enable))
116        return bool(self.card.get_i(SPC_ENABLEOUT0 + (SPC_ENABLEOUT1 - SPC_ENABLEOUT0) * self.index))

Enables the analog front-end of the channel of the card (see register SPC_ENABLEOUT in the manual)

Parameters
  • enable (bool): Turn-on (True) or off (False) the spezific channel
Returns
  • bool: The enable state of the specific channel
def enable_out(self, enable: bool = None) -> bool:
 99    def enable(self, enable : bool = None) -> bool:
100        """
101        Enables the analog front-end of the channel of the card (see register `SPC_ENABLEOUT` in the manual)
102    
103        Parameters
104        ----------
105        enable : bool
106            Turn-on (True) or off (False) the spezific channel
107
108        Returns
109        -------
110        bool
111            The enable state of the specific channel
112        """
113
114        if enable is not None:
115            self.card.set_i(SPC_ENABLEOUT0 + (SPC_ENABLEOUT1 - SPC_ENABLEOUT0) * self.index, int(enable))
116        return bool(self.card.get_i(SPC_ENABLEOUT0 + (SPC_ENABLEOUT1 - SPC_ENABLEOUT0) * self.index))

Enables the analog front-end of the channel of the card (see register SPC_ENABLEOUT in the manual)

Parameters
  • enable (bool): Turn-on (True) or off (False) the spezific channel
Returns
  • bool: The enable state of the specific channel
def path(self, value: int = None) -> int:
119    def path(self, value : int = None) -> int:
120        """
121        Sets the input path of the channel of the card (see register `SPC_PATH0` in the manual).
122        To make the read back of the coupling offset compensation setting possible, we also set 
123        the register `SPC_READAIPATH` to the same value.
124    
125        Parameters
126        ----------
127        value : int
128            The input path of the specific channel
129        
130        Returns
131        -------
132        int
133            The input path of the specific channel
134        """
135
136        if value is not None:
137            self.card.set_i(SPC_PATH0 + (SPC_PATH1 - SPC_PATH0) * self.index, value)
138            self.card.set_i(SPC_READAIPATH, value)
139        return self.card.get_i(SPC_PATH0 + (SPC_PATH1 - SPC_PATH0) * self.index)

Sets the input path of the channel of the card (see register SPC_PATH0 in the manual). To make the read back of the coupling offset compensation setting possible, we also set the register SPC_READAIPATH to the same value.

Parameters
  • value (int): The input path of the specific channel
Returns
  • int: The input path of the specific channel
def amp(self, value: int = None, return_unit=None) -> int:
141    def amp(self, value : int = None, return_unit = None) -> int:
142        """
143        Sets the output/input range (amplitude) of the analog front-end of the channel of the card in mV (see register `SPC_AMP` in the manual)
144    
145        Parameters
146        ----------
147        value : int
148            The output range (amplitude) of the specific channel in millivolts
149        unit : pint.Unit = None
150            The unit of the return value
151            
152        Returns
153        -------
154        int | pint.Quantity
155            The output range (amplitude) of the specific channel in millivolts or the unit specified
156        """
157
158        if value is not None:
159            if isinstance(value, pint.Quantity):
160                value = self.voltage_conversion(value)
161            self._conversion_amp = UnitConversion.force_unit(value, units.mV)
162            value = UnitConversion.convert(value, units.mV, int)
163            self.card.set_i(SPC_AMP0 + (SPC_AMP1 - SPC_AMP0) * self.index, value)
164        value = self.card.get_i(SPC_AMP0 + (SPC_AMP1 - SPC_AMP0) * self.index)
165        value = UnitConversion.to_unit(value * units.mV, return_unit)
166        return value

Sets the output/input range (amplitude) of the analog front-end of the channel of the card in mV (see register SPC_AMP in the manual)

Parameters
  • value (int): The output range (amplitude) of the specific channel in millivolts
  • unit (pint.Unit = None): The unit of the return value
Returns
  • int | pint.Quantity: The output range (amplitude) of the specific channel in millivolts or the unit specified
def offset(self, value: int = None, return_unit=None) -> int:
168    def offset(self, value : int = None, return_unit = None) -> int:
169        """
170        Sets the offset of the analog front-end of the channel of the card in % of the full range o rmV (see register `SPC_OFFS0` in the manual)
171        If the value is given and has a unit, then this unit is converted to the unit of the card (mV or %)
172    
173        Parameters
174        ----------
175        value : int | pint.Quantity = None
176            The offset of the specific channel as integer in % or as a Quantity in % or mV
177        unit : pint.Unit = None
178            The unit of the return value
179            
180        Returns
181        -------
182        int | pint.Quantity
183            The offset of the specific channel in % or the unit specified by return_unit
184        """
185
186        # Analog in cards are programmed in percent of the full range and analog output cards in mV (in the M2p, M4i/x and M5i families)
187        card_unit = 1
188        fnc_type = self.card.function_type()
189        if fnc_type == SPCM_TYPE_AI:
190            card_unit = units.percent
191        elif fnc_type == SPCM_TYPE_AO:
192            card_unit = units.mV
193
194        if value is not None:
195            # The user gives a value as a Quantity
196            if isinstance(value, pint.Quantity):
197                if fnc_type == SPCM_TYPE_AO:
198                    # The card expects a value in mV
199                    if value.check('[]'):
200                        # Convert from percent to mV
201                        value = (value * self._conversion_amp).to(card_unit)
202                    else:
203                        value = value.to(card_unit)
204                elif fnc_type == SPCM_TYPE_AI:
205                    # The card expects a value in percent
206                    if value.check('[electric_potential]'):
207                        # Convert from mV to percent
208                        value = (value / self._conversion_amp).to(card_unit)
209                    else:
210                        value = value.to(card_unit)
211            else:
212                # Value is given as a number
213                pass
214
215            value = UnitConversion.convert(value, card_unit, int)
216            self.card.set_i(SPC_OFFS0 + (SPC_OFFS1 - SPC_OFFS0) * self.index, value)
217        
218        return_value = self.card.get_i(SPC_OFFS0 + (SPC_OFFS1 - SPC_OFFS0) * self.index)
219        # Turn the return value into a quantity
220        return_quantity = UnitConversion.to_unit(return_value, return_unit)
221        # Save the conversion offset to be able to convert the data to a quantity with the correct unit
222        self._conversion_offset = UnitConversion.force_unit(return_value, card_unit)
223        return return_quantity

Sets the offset of the analog front-end of the channel of the card in % of the full range o rmV (see register SPC_OFFS0 in the manual) If the value is given and has a unit, then this unit is converted to the unit of the card (mV or %)

Parameters
  • value (int | pint.Quantity = None): The offset of the specific channel as integer in % or as a Quantity in % or mV
  • unit (pint.Unit = None): The unit of the return value
Returns
  • int | pint.Quantity: The offset of the specific channel in % or the unit specified by return_unit
def special_clock_adjust(self, special_clock: bool = False) -> float:
225    def special_clock_adjust(self, special_clock : bool = False) -> float:
226        """
227        Gets the special clock adjust value of the channel (see register `SPC_SPECIALCLOCK_ADJUST0` in the manual)
228        
229        Returns
230        -------
231        float
232            The special clock adjust value of the channel
233        """
234
235        adjust = 1.0
236        if special_clock:
237            # If the card is in special clock mode, we need to adjust the conversion factor
238            adjust = self.card.get_d(SPC_SPECIALCLOCK_ADJUST0 + (SPC_SPECIALCLOCK_ADJUST1 - SPC_SPECIALCLOCK_ADJUST0) * self.index)
239        return adjust

Gets the special clock adjust value of the channel (see register SPC_SPECIALCLOCK_ADJUST0 in the manual)

Returns
  • float: The special clock adjust value of the channel
def convert_data( self, data: numpy.ndarray[tuple[int, ...], numpy.dtype[+_ScalarType_co]], return_unit: pint.registry.Unit = <Unit('millivolt')>, averages: int = 1) -> numpy.ndarray[tuple[int, ...], numpy.dtype[+_ScalarType_co]]:
241    def convert_data(self, data : npt.NDArray, return_unit : pint.Unit = units.mV, averages : int = 1) -> npt.NDArray:
242        """
243        Converts the data to the correct unit in units of electrical potential
244        
245        Parameters
246        ----------
247        data : numpy.ndarray
248            The data to be converted
249        return_unit : pint.Unit = units.mV
250            The unit of the return value
251        averages : int = 1
252            The number of averages that have been done to the data and should be taken into account to convert the data
253            
254        Returns
255        -------
256        numpy.ndarray
257            The converted data in units of electrical potential
258        """
259
260        max_value = self.card.max_sample_value() * averages
261        if self._conversion_offset.check('[]'):
262            return_data = (data / max_value - self._conversion_offset) * (self._conversion_amp * self._conversion_clock)
263        else:
264            return_data = (data / max_value) * (self._conversion_amp * self._conversion_clock) - self._conversion_offset
265        return_data = UnitConversion.to_unit(return_data, return_unit)
266        return return_data

Converts the data to the correct unit in units of electrical potential

Parameters
  • data (numpy.ndarray): The data to be converted
  • return_unit (pint.Unit = units.mV): The unit of the return value
  • averages (int = 1): The number of averages that have been done to the data and should be taken into account to convert the data
Returns
  • numpy.ndarray: The converted data in units of electrical potential
def reconvert_data( self, data: numpy.ndarray[tuple[int, ...], numpy.dtype[+_ScalarType_co]]) -> numpy.ndarray[tuple[int, ...], numpy.dtype[+_ScalarType_co]]:
268    def reconvert_data(self, data : npt.NDArray) -> npt.NDArray:
269        """
270        Convert data with units back to integer values in units of electrical potential
271        
272        Parameters
273        ----------
274        data : numpy.ndarray
275            The data to be reconverted
276            
277        Returns
278        -------
279        numpy.ndarray
280            The reconverted data as integer in mV
281        """
282
283        if self._conversion_offset.check('[]'):
284            return_data = int((data / (self._conversion_amp * self._conversion_clock) + self._conversion_offset) * self.card.max_sample_value())
285        else:
286            return_data = int(((data + self._conversion_offset) / (self._conversion_amp * self._conversion_clock)) * self.card.max_sample_value())
287        return return_data

Convert data with units back to integer values in units of electrical potential

Parameters
  • data (numpy.ndarray): The data to be reconverted
Returns
  • numpy.ndarray: The reconverted data as integer in mV
def termination(self, value: int) -> None:
289    def termination(self, value : int) -> None:
290        """
291        Sets the termination of the analog front-end of the channel of the card (see register `SPC_50OHM0` in the manual)
292    
293        Parameters
294        ----------
295        value : int | bool
296            The termination of the specific channel
297        """
298
299        self.card.set_i(SPC_50OHM0 + (SPC_50OHM1 - SPC_50OHM0) * self.index, int(value))

Sets the termination of the analog front-end of the channel of the card (see register SPC_50OHM0 in the manual)

Parameters
  • value (int | bool): The termination of the specific channel
def get_termination(self) -> int:
301    def get_termination(self) -> int:
302        """
303        Gets the termination of the analog front-end of the channel of the card (see register `SPC_50OHM0` in the manual)
304            
305        Returns
306        -------
307        int
308            The termination of the specific channel
309        """
310
311        return self.card.get_i(SPC_50OHM0 + (SPC_50OHM1 - SPC_50OHM0) * self.index)

Gets the termination of the analog front-end of the channel of the card (see register SPC_50OHM0 in the manual)

Returns
  • int: The termination of the specific channel
def coupling(self, value: int = None) -> int:
313    def coupling(self, value : int = None) -> int:
314        """
315        Sets the coupling of the analog front-end of the channel of the card (see register `SPC_ACDC0` in the manual)
316    
317        Parameters
318        ----------
319        value : int
320            The coupling of the specific channel
321            
322        Returns
323        -------
324        int
325            The coupling of the specific channel
326        """
327
328        if value is not None:
329            self.card.set_i(SPC_ACDC0 + (SPC_ACDC1 - SPC_ACDC0) * self.index, value)
330        return self.card.get_i(SPC_ACDC0 + (SPC_ACDC1 - SPC_ACDC0) * self.index)

Sets the coupling of the analog front-end of the channel of the card (see register SPC_ACDC0 in the manual)

Parameters
  • value (int): The coupling of the specific channel
Returns
  • int: The coupling of the specific channel
def coupling_offset_compensation(self, value: int = None) -> int:
332    def coupling_offset_compensation(self, value : int = None) -> int:
333        """
334        Enables or disables the coupling offset compensation of the analog front-end of the channel of the card (see register `SPC_ACDC_OFFS_COMPENSATION0` in the manual)
335    
336        Parameters
337        ----------
338        value : int
339            Enables the coupling offset compensation of the specific channel
340            
341        Returns
342        -------
343        int
344            return if the coupling offset compensation of the specific channel is enabled ("1") or disabled ("0")
345        """
346
347        if value is not None:
348            self.card.set_i(SPC_ACDC_OFFS_COMPENSATION0 + (SPC_ACDC_OFFS_COMPENSATION1 - SPC_ACDC_OFFS_COMPENSATION0) * self.index, value)
349        return self.card.get_i(SPC_ACDC_OFFS_COMPENSATION0 + (SPC_ACDC_OFFS_COMPENSATION1 - SPC_ACDC_OFFS_COMPENSATION0) * self.index)

Enables or disables the coupling offset compensation of the analog front-end of the channel of the card (see register SPC_ACDC_OFFS_COMPENSATION0 in the manual)

Parameters
  • value (int): Enables the coupling offset compensation of the specific channel
Returns
  • int: return if the coupling offset compensation of the specific channel is enabled ("1") or disabled ("0")
def filter(self, value: int = None) -> int:
351    def filter(self, value : int = None) -> int:
352        """
353        Sets the filter of the analog front-end of the channel of the card (see register `SPC_FILTER0` in the manual)
354    
355        Parameters
356        ----------
357        value : int
358            The filter of the specific channel
359            
360        Returns
361        -------
362        int
363            The filter of the specific channel
364        """
365
366        if value is not None:
367            self.card.set_i(SPC_FILTER0 + (SPC_FILTER1 - SPC_FILTER0) * self.index, value)
368        return self.card.get_i(SPC_FILTER0 + (SPC_FILTER1 - SPC_FILTER0) * self.index)

Sets the filter of the analog front-end of the channel of the card (see register SPC_FILTER0 in the manual)

Parameters
  • value (int): The filter of the specific channel
Returns
  • int: The filter of the specific channel
def stop_level(self, value: int = None) -> int:
370    def stop_level(self, value : int = None) -> int:
371        """
372        Usually the used outputs of the analog generation boards are set to zero level after replay. 
373        This is in most cases adequate. In some cases it can be necessary to hold the last sample,
374        to output the maximum positive level or maximum negative level after replay. The stoplevel will 
375        stay on the defined level until the next output has been made. With this function
376        you can define the behavior after replay (see register `SPC_CH0_STOPLEVEL` in the manual)
377    
378        Parameters
379        ----------
380        value : int
381            The wanted stop behaviour
382
383        Returns
384        -------
385        int
386            The stop behaviour of the specific channel
387        """
388
389        if value is not None:
390            self.card.set_i(SPC_CH0_STOPLEVEL + self.index * (SPC_CH1_STOPLEVEL - SPC_CH0_STOPLEVEL), value)
391        return self.card.get_i(SPC_CH0_STOPLEVEL + self.index * (SPC_CH1_STOPLEVEL - SPC_CH0_STOPLEVEL))

Usually the used outputs of the analog generation boards are set to zero level after replay. This is in most cases adequate. In some cases it can be necessary to hold the last sample, to output the maximum positive level or maximum negative level after replay. The stoplevel will stay on the defined level until the next output has been made. With this function you can define the behavior after replay (see register SPC_CH0_STOPLEVEL in the manual)

Parameters
  • value (int): The wanted stop behaviour
Returns
  • int: The stop behaviour of the specific channel
def custom_stop(self, value: int = None) -> int:
393    def custom_stop(self, value : int = None) -> int:
394        """
395        Allows to define a 16bit wide custom level per channel for the analog output to enter in pauses. The sample format is 
396        exactly the same as during replay, as described in the „sample format“ section.
397        When synchronous digital bits are replayed along, the custom level must include these as well and therefore allows to 
398        set a custom level for each multi-purpose line separately. (see register `SPC_CH0_CUSTOM_STOP` in the manual)
399    
400        Parameters
401        ----------
402        value : int
403            The custom stop value
404
405        Returns
406        -------
407        int
408            The custom stop value of the specific channel
409        
410        TODO: change this to a specific unit?
411        """
412
413        if value is not None:
414            self.card.set_i(SPC_CH0_CUSTOM_STOP + self.index * (SPC_CH1_CUSTOM_STOP - SPC_CH0_CUSTOM_STOP), value)
415        return self.card.get_i(SPC_CH0_CUSTOM_STOP + self.index * (SPC_CH1_CUSTOM_STOP - SPC_CH0_CUSTOM_STOP))

Allows to define a 16bit wide custom level per channel for the analog output to enter in pauses. The sample format is exactly the same as during replay, as described in the „sample format“ section. When synchronous digital bits are replayed along, the custom level must include these as well and therefore allows to set a custom level for each multi-purpose line separately. (see register SPC_CH0_CUSTOM_STOP in the manual)

Parameters
  • value (int): The custom stop value
Returns
  • int: The custom stop value of the specific channel
  • TODO (change this to a specific unit?):
def ch_mask(self) -> int:
417    def ch_mask(self) -> int:
418        """
419        Gets mask for the "or"- or "and"-mask
420
421        Returns
422        -------
423        int
424            The mask for the "or"- or "and"-mask
425        """
426
427        return 1 << self.index

Gets mask for the "or"- or "and"-mask

Returns
  • int: The mask for the "or"- or "and"-mask
def output_load(self, value: pint.registry.Quantity = None) -> pint.registry.Quantity:
429    def output_load(self, value : pint.Quantity = None) -> pint.Quantity:
430        """
431        Sets the electrical load of the user system connect the channel of the card. This is important for the correct
432        calculation of the output power. Typically, the load would be 50 Ohms, but it can be different.
433
434        Parameters
435        ----------
436        value : pint.Quantity
437            The electrical load connected by the user to the specific channel
438
439        Returns
440        -------
441        pint.Quantity
442            The electrical load connected by the user to the specific channel
443        """
444        if value is not None:
445            self._output_load = value
446        return self._output_load

Sets the electrical load of the user system connect the channel of the card. This is important for the correct calculation of the output power. Typically, the load would be 50 Ohms, but it can be different.

Parameters
  • value (pint.Quantity): The electrical load connected by the user to the specific channel
Returns
  • pint.Quantity: The electrical load connected by the user to the specific channel
def voltage_conversion(self, value: pint.registry.Quantity) -> pint.registry.Quantity:
448    def voltage_conversion(self, value : pint.Quantity) -> pint.Quantity:
449        """
450        Convert the voltage that is needed at a certain output load to the voltage setting of the card if the load would be 50 Ohm
451
452        Parameters
453        ----------
454        value : pint.Quantity
455            The voltage that is needed at a certain output load
456
457        Returns
458        -------
459        pint.Quantity
460            The corresponding voltage at an output load of 50 Ohm
461        """
462
463        # The two at the end is because the value expected by the card is defined for a 50 Ohm load
464        if self._output_load == np.inf * units.ohm:
465            return value / 2
466        return value / (self._output_load / (self._output_load + self._series_impedance)) / 2

Convert the voltage that is needed at a certain output load to the voltage setting of the card if the load would be 50 Ohm

Parameters
  • value (pint.Quantity): The voltage that is needed at a certain output load
Returns
  • pint.Quantity: The corresponding voltage at an output load of 50 Ohm
def to_amplitude_fraction(self, value) -> float:
468    def to_amplitude_fraction(self, value) -> float:
469        """
470        Convert the voltage, percentage or power to percentage of the full range of the card
471
472        Parameters
473        ----------
474        value : pint.Quantity | float
475            The voltage that should be outputted at a certain output load
476
477        Returns
478        -------
479        float
480            The corresponding fraction of the full range of the card
481        """
482
483        if isinstance(value, units.Quantity) and value.check("[power]"):
484            # U_pk = U_rms * sqrt(2)
485            value = np.sqrt(2 * value.to('mW') * self._output_load) / self._conversion_amp * 100 * units.percent
486        elif isinstance(value, units.Quantity) and value.check("[electric_potential]"):
487            # value in U_pk
488            value = self.voltage_conversion(value) / self._conversion_amp * 100 * units.percent
489        value = UnitConversion.convert(value, units.fraction, float, rounding=None)
490        return value

Convert the voltage, percentage or power to percentage of the full range of the card

Parameters
  • value (pint.Quantity | float): The voltage that should be outputted at a certain output load
Returns
  • float: The corresponding fraction of the full range of the card
def from_amplitude_fraction( self, fraction, return_unit: pint.registry.Quantity = None) -> pint.registry.Quantity:
492    def from_amplitude_fraction(self, fraction, return_unit : pint.Quantity = None) -> pint.Quantity:
493        """
494        Convert the percentage of the full range to voltage, percentage or power
495
496        Parameters
497        ----------
498        fraction : float
499            The percentage of the full range of the card
500        return_unit : pint.Quantity
501            The unit of the return value
502
503        Returns
504        -------
505        pint.Quantity
506            The corresponding voltage, percentage or power
507        """
508
509        return_value = fraction
510        if isinstance(return_unit, units.Unit) and (1*return_unit).check("[power]"):
511            return_value = (np.power(self._conversion_amp * fraction, 2) / self._output_load / 2).to(return_unit)
512            # U_pk = U_rms * sqrt(2)
513        elif isinstance(return_unit, units.Unit) and (1*return_unit).check("[electric_potential]"):
514            return_value = (self._conversion_amp * fraction / (100 * units.percent)).to(return_unit)
515            # value in U_pk
516            # value = self.voltage_conversion(value) / self._conversion_amp * 100 * units.percent
517        elif isinstance(return_unit, units.Unit) and (1*return_unit).check("[]"):
518            return_value = UnitConversion.force_unit(fraction, return_unit)
519        return return_value

Convert the percentage of the full range to voltage, percentage or power

Parameters
  • fraction (float): The percentage of the full range of the card
  • return_unit (pint.Quantity): The unit of the return value
Returns
  • pint.Quantity: The corresponding voltage, percentage or power
class Clock(spcm.CardFunctionality):
 12class Clock(CardFunctionality):
 13    """a higher-level abstraction of the CardFunctionality class to implement the Card's clock engine"""
 14    
 15    def __str__(self) -> str:
 16        """
 17        String representation of the Clock class
 18    
 19        Returns
 20        -------
 21        str
 22            String representation of the Clock class
 23        """
 24        
 25        return f"Clock(card={self.card})"
 26    
 27    __repr__ = __str__
 28    
 29    def write_setup(self) -> None:
 30        """Write the setup to the card"""
 31        self.card.write_setup()
 32    
 33    
 34    def mode(self, mode : int = None) -> int:
 35        """
 36        Set the clock mode of the card (see register `SPC_CLOCKMODE` in the manual)
 37    
 38        Parameters
 39        ----------
 40        mode : int
 41            The clock mode of the card
 42        
 43        Returns
 44        -------
 45        int
 46            The clock mode of the card
 47        """
 48
 49        if mode is not None:
 50            self.card.set_i(SPC_CLOCKMODE, mode)
 51        return self.card.get_i(SPC_CLOCKMODE)
 52    
 53    def special_clock(self, special_clock : int = None) -> int:
 54        """
 55        Set the special clock mode of the card (see register `SPC_SPECIALCLOCK` in the manual)
 56    
 57        Parameters
 58        ----------
 59        special_clock : int
 60            The special clock mode of the card
 61        
 62        Returns
 63        -------
 64        int
 65            The special clock mode of the card
 66        """
 67        
 68        if special_clock is not None:
 69            self.card.set_i(SPC_SPECIALCLOCK, special_clock)
 70        return self.card.get_i(SPC_SPECIALCLOCK)
 71
 72    def auto_adjust(self):
 73        """
 74        Automatically adjust the clock settings of the card (see register `SPC_ADJ_AUTOADJ` in the manual)
 75        
 76        Returns
 77        -------
 78        None
 79        """
 80        
 81        self.card.set_i(SPC_ADJ_AUTOADJ, ADJ_SPECIAL_CLOCK)
 82
 83    def special_clock_adjust(self, channel : int) -> float:
 84        """
 85        Get the sample correction factor obtained from the last special clock calibration (see register `SPC_SPECIALCLOCK_ADJUST0` in the manual)
 86
 87        Parameters
 88        ----------
 89        channel : int
 90            The channel number to get the adjustment for
 91
 92        Returns
 93        -------
 94        float
 95            The sample correction factor for the specified channel
 96        """
 97
 98        return self.card.get_d(SPC_SPECIALCLOCK_ADJUST0 + (SPC_SPECIALCLOCK_ADJUST1 - SPC_SPECIALCLOCK_ADJUST0) * int(channel))
 99    
100    def max_sample_rate(self, return_unit = None) -> int:
101        """
102        Returns the maximum sample rate of the active card (see register `SPC_MIINST_MAXADCLOCK` in the manual)
103    
104        Returns
105        -------
106        int
107        """
108        
109        max_sr = self.card.get_i(SPC_MIINST_MAXADCLOCK)
110        if return_unit is not None: max_sr = UnitConversion.to_unit(max_sr * units.Hz, return_unit)
111        return max_sr
112
113    def sample_rate(self, sample_rate = 0, max : bool = False, special_clock : bool = False, auto_adjust : bool = False, return_unit = None) -> int:
114        """
115        Sets or gets the current sample rate of the handled card (see register `SPC_SAMPLERATE` in the manual)
116
117        Parameters
118        ----------
119        sample_rate : int | pint.Quantity = 0
120            if the parameter sample_rate is given with the function call, then the card's sample rate is set to that value
121        max : bool = False
122            if max is True, the method sets the maximum sample rate of the card
123        special_clock : bool = False
124            if special_clock is True, the method sets the special clock mode of the card
125        auto_adjust : bool = False
126            if auto_adjust is True, the method automatically calibrates the values of the ADC at this specific special clock sampling rate
127        unit : pint.Unit = None
128            the unit of the sample rate, by default None
129    
130        Returns
131        -------
132        int
133            the current sample rate in Samples/s
134        """
135        
136        if max: sample_rate = self.max_sample_rate()
137        if special_clock: self.special_clock(1)
138        if sample_rate:
139            if isinstance(sample_rate, units.Quantity) and sample_rate.check("[]"):
140                max_sr = self.max_sample_rate()
141                sample_rate = sample_rate.to_base_units().magnitude * max_sr
142            sample_rate = UnitConversion.convert(sample_rate, units.Hz, int)
143            self.card.set_i(SPC_SAMPLERATE, int(sample_rate))
144        return_value = self.card.get_i(SPC_SAMPLERATE)
145        if special_clock and auto_adjust: self.auto_adjust()
146        if return_unit is not None: return_value = UnitConversion.to_unit(return_value * units.Hz, return_unit)
147        return return_value
148    
149    def oversampling_factor(self) -> int:
150        """
151        Returns the oversampling factor of the card (see register `SPC_OVERSAMPLINGFACTOR` in the manual)
152        
153        Returns
154        -------
155        int
156            the oversampling factor of the card
157        """
158        
159        return self.card.get_i(SPC_OVERSAMPLINGFACTOR)
160
161    def clock_output(self, clock_output : int = None) -> int:
162        """
163        Set the clock output of the card (see register `SPC_CLOCKOUT` in the manual)
164        
165        Parameters
166        ----------
167        clock_output : int
168            the clock output of the card
169        
170        Returns
171        -------
172        int
173            the clock output of the card
174        """
175        
176        if clock_output is not None:
177            self.card.set_i(SPC_CLOCKOUT, int(clock_output))
178        return self.card.get_i(SPC_CLOCKOUT)
179    output = clock_output
180
181    def clock_output_frequency(self, return_unit = None) -> int:
182        """
183        Returns the clock output frequency of the card (see register `SPC_CLOCKOUTFREQUENCY` in the manual)
184        
185        Parameters
186        ----------
187        return_unit : pint.Unit = None
188            the unit of the clock output frequency
189        
190        Returns
191        -------
192        int | pint.Quantity
193            the clock output frequency of the card
194        """
195        
196        value = self.card.get_i(SPC_CLOCKOUTFREQUENCY)
197        value = UnitConversion.to_unit(value * units.Hz, return_unit)
198        return value
199    
200    def reference_clock(self, reference_clock : int = None) -> int:
201        """
202        Set the reference clock of the card (see register `SPC_REFERENCECLOCK` in the manual)
203        
204        Parameters
205        ----------
206        reference_clock : int | pint.Quantity
207            the reference clock of the card in Hz
208        
209        Returns
210        -------
211        int
212            the reference clock of the card in Hz
213        """
214        
215        if reference_clock is not None:
216            reference_clock = UnitConversion.convert(reference_clock, units.Hz, int)
217            self.card.set_i(SPC_REFERENCECLOCK, reference_clock)
218        return self.card.get_i(SPC_REFERENCECLOCK)
219    
220    def termination(self, termination : int = None) -> int:
221        """
222        Set the termination for the clock input of the card (see register `SPC_CLOCK50OHM` in the manual)
223        
224        Parameters
225        ----------
226        termination : int | bool
227            the termination of the card
228        
229        Returns
230        -------
231        int
232            the termination of the card
233        """
234        
235        if termination is not None:
236            self.card.set_i(SPC_CLOCK50OHM, int(termination))
237        return self.card.get_i(SPC_CLOCK50OHM)
238    
239    def threshold(self, value : int = None, return_unit = None) -> int:
240        """
241        Set the clock threshold of the card (see register `SPC_CLOCKTHRESHOLD` in the manual)
242        
243        Parameters
244        ----------
245        value : int
246            the clock threshold of the card
247        return_unit : pint.Unit = None
248            the unit of the clock threshold
249        
250        Returns
251        -------
252        int | pint.Quantity
253            the clock threshold of the card
254        """
255        
256        if value is not None:
257            value = UnitConversion.convert(value, units.mV, int)
258            self.card.set_i(SPC_CLOCK_THRESHOLD, int(value))
259        value = self.card.get_i(SPC_CLOCK_THRESHOLD)
260        value = UnitConversion.to_unit(value * units.mV, return_unit)
261        return value
262    
263    def threshold_min(self, return_unit = None) -> int:
264        """
265        Returns the minimum clock threshold of the card (see register `SPC_CLOCK_AVAILTHRESHOLD_MIN` in the manual)
266
267        Parameters
268        ----------
269        return_unit : pint.Unit = None
270            the unit of the return clock threshold
271        
272        Returns
273        -------
274        int
275            the minimum clock threshold of the card
276        """
277        
278        value = self.card.get_i(SPC_CLOCK_AVAILTHRESHOLD_MIN)
279        value = UnitConversion.to_unit(value * units.mV, return_unit)
280        return value
281    
282    def threshold_max(self, return_unit = None) -> int:
283        """
284        Returns the maximum clock threshold of the card (see register `SPC_CLOCK_AVAILTHRESHOLD_MAX` in the manual)
285
286        Parameters
287        ----------
288        return_unit : pint.Unit = None
289            the unit of the return clock threshold
290        
291        Returns
292        -------
293        int
294            the maximum clock threshold of the card
295        """
296        
297        value = self.card.get_i(SPC_CLOCK_AVAILTHRESHOLD_MAX)
298        value = UnitConversion.to_unit(value * units.mV, return_unit)
299        return value
300    
301    def threshold_step(self, return_unit = None) -> int:
302        """
303        Returns the step of the clock threshold of the card (see register `SPC_CLOCK_AVAILTHRESHOLD_STEP` in the manual)
304
305        Parameters
306        ----------
307        return_unit : pint.Unit = None
308            the unit of the return clock threshold
309        
310        Returns
311        -------
312        int
313            the step of the clock threshold of the card
314        """
315        
316        value = self.card.get_i(SPC_CLOCK_AVAILTHRESHOLD_STEP)
317        value = UnitConversion.to_unit(value * units.mV, return_unit)
318        return value
319    
320    def edge(self, edge : int = None) -> int:
321        """
322        Set the clock edge of the card (see register `SPC_CLOCK_EDGE` in the manual)
323        
324        Parameters
325        ----------
326        edge : int
327            the clock edge of the card
328        
329        Returns
330        -------
331        int
332            the clock edge of the card
333        """
334        
335        if edge is not None:
336            self.card.set_i(SPC_CLOCK_EDGE, int(edge))
337        return self.card.get_i(SPC_CLOCK_EDGE)
338
339    def delay(self, delay : int = None, return_unit = None):
340        """
341        Set the clock delay of the card (see register `SPC_CLOCK_DELAY` in the manual)
342
343        Parameters
344        ----------
345        delay : int | pint.Quantity | pint.Unit = None
346            the clock delay of the card
347        return_unit : pint.Unit = None
348            the unit of the clock delay
349
350        Returns
351        -------
352        int | pint.Unit
353            the clock delay of the card
354        """
355
356        if delay is not None:
357            delay = UnitConversion.convert(delay, units.ps, int)
358            self.card.set_i(SPC_CLOCK_DELAY, int(delay))
359        delay = self.card.get_i(SPC_CLOCK_DELAY)
360        delay = UnitConversion.to_unit(delay * units.ps, return_unit)
361        return delay
362    
363    def delay_min(self, return_unit = None) -> int:
364        """
365        Returns the minimum clock delay of the card (see register `SPC_CLOCK_AVAILDELAY_MIN` in the manual)
366
367        Parameters
368        ----------
369        return_unit : pint.Unit = None
370            the unit of the return clock delay
371
372        Returns
373        -------
374        int | pint.Quantity
375            the minimum clock delay of the card
376        """
377        
378        value = self.card.get_i(SPC_CLOCK_AVAILDELAY_MIN)
379        value = UnitConversion.to_unit(value * units.ps, return_unit)
380        return value
381    
382    def delay_max(self, return_unit = None) -> int:
383        """
384        Returns the maximum clock delay of the card (see register `SPC_CLOCK_AVAILDELAY_MAX` in the manual)
385
386        Parameters
387        ----------
388        return_unit : pint.Unit = None
389            the unit of the return clock delay
390
391        Returns
392        -------
393        int | pint.Quantity
394            the maximum clock delay of the card
395        """
396        
397        value = self.card.get_i(SPC_CLOCK_AVAILDELAY_MAX)
398        value = UnitConversion.to_unit(value * units.ps, return_unit)
399        return value
400    
401    def delay_step(self, return_unit = None) -> int:
402        """
403        Returns the step of the clock delay of the card (see register `SPC_CLOCK_AVAILDELAY_STEP` in the manual)
404
405        Parameters
406        ----------
407        return_unit : pint.Unit = None
408            the unit of the return clock delay
409
410        Returns
411        -------
412        int | pint.Quantity
413            the step of the clock delay of the card
414        """
415        
416        value = self.card.get_i(SPC_CLOCK_AVAILDELAY_STEP)
417        value = UnitConversion.to_unit(value * units.ps, return_unit)
418        return value

a higher-level abstraction of the CardFunctionality class to implement the Card's clock engine

def write_setup(self) -> None:
29    def write_setup(self) -> None:
30        """Write the setup to the card"""
31        self.card.write_setup()

Write the setup to the card

def mode(self, mode: int = None) -> int:
34    def mode(self, mode : int = None) -> int:
35        """
36        Set the clock mode of the card (see register `SPC_CLOCKMODE` in the manual)
37    
38        Parameters
39        ----------
40        mode : int
41            The clock mode of the card
42        
43        Returns
44        -------
45        int
46            The clock mode of the card
47        """
48
49        if mode is not None:
50            self.card.set_i(SPC_CLOCKMODE, mode)
51        return self.card.get_i(SPC_CLOCKMODE)

Set the clock mode of the card (see register SPC_CLOCKMODE in the manual)

Parameters
  • mode (int): The clock mode of the card
Returns
  • int: The clock mode of the card
def special_clock(self, special_clock: int = None) -> int:
53    def special_clock(self, special_clock : int = None) -> int:
54        """
55        Set the special clock mode of the card (see register `SPC_SPECIALCLOCK` in the manual)
56    
57        Parameters
58        ----------
59        special_clock : int
60            The special clock mode of the card
61        
62        Returns
63        -------
64        int
65            The special clock mode of the card
66        """
67        
68        if special_clock is not None:
69            self.card.set_i(SPC_SPECIALCLOCK, special_clock)
70        return self.card.get_i(SPC_SPECIALCLOCK)

Set the special clock mode of the card (see register SPC_SPECIALCLOCK in the manual)

Parameters
  • special_clock (int): The special clock mode of the card
Returns
  • int: The special clock mode of the card
def auto_adjust(self):
72    def auto_adjust(self):
73        """
74        Automatically adjust the clock settings of the card (see register `SPC_ADJ_AUTOADJ` in the manual)
75        
76        Returns
77        -------
78        None
79        """
80        
81        self.card.set_i(SPC_ADJ_AUTOADJ, ADJ_SPECIAL_CLOCK)

Automatically adjust the clock settings of the card (see register SPC_ADJ_AUTOADJ in the manual)

Returns
  • None
def special_clock_adjust(self, channel: int) -> float:
83    def special_clock_adjust(self, channel : int) -> float:
84        """
85        Get the sample correction factor obtained from the last special clock calibration (see register `SPC_SPECIALCLOCK_ADJUST0` in the manual)
86
87        Parameters
88        ----------
89        channel : int
90            The channel number to get the adjustment for
91
92        Returns
93        -------
94        float
95            The sample correction factor for the specified channel
96        """
97
98        return self.card.get_d(SPC_SPECIALCLOCK_ADJUST0 + (SPC_SPECIALCLOCK_ADJUST1 - SPC_SPECIALCLOCK_ADJUST0) * int(channel))

Get the sample correction factor obtained from the last special clock calibration (see register SPC_SPECIALCLOCK_ADJUST0 in the manual)

Parameters
  • channel (int): The channel number to get the adjustment for
Returns
  • float: The sample correction factor for the specified channel
def max_sample_rate(self, return_unit=None) -> int:
100    def max_sample_rate(self, return_unit = None) -> int:
101        """
102        Returns the maximum sample rate of the active card (see register `SPC_MIINST_MAXADCLOCK` in the manual)
103    
104        Returns
105        -------
106        int
107        """
108        
109        max_sr = self.card.get_i(SPC_MIINST_MAXADCLOCK)
110        if return_unit is not None: max_sr = UnitConversion.to_unit(max_sr * units.Hz, return_unit)
111        return max_sr

Returns the maximum sample rate of the active card (see register SPC_MIINST_MAXADCLOCK in the manual)

Returns
  • int
def sample_rate( self, sample_rate=0, max: bool = False, special_clock: bool = False, auto_adjust: bool = False, return_unit=None) -> int:
113    def sample_rate(self, sample_rate = 0, max : bool = False, special_clock : bool = False, auto_adjust : bool = False, return_unit = None) -> int:
114        """
115        Sets or gets the current sample rate of the handled card (see register `SPC_SAMPLERATE` in the manual)
116
117        Parameters
118        ----------
119        sample_rate : int | pint.Quantity = 0
120            if the parameter sample_rate is given with the function call, then the card's sample rate is set to that value
121        max : bool = False
122            if max is True, the method sets the maximum sample rate of the card
123        special_clock : bool = False
124            if special_clock is True, the method sets the special clock mode of the card
125        auto_adjust : bool = False
126            if auto_adjust is True, the method automatically calibrates the values of the ADC at this specific special clock sampling rate
127        unit : pint.Unit = None
128            the unit of the sample rate, by default None
129    
130        Returns
131        -------
132        int
133            the current sample rate in Samples/s
134        """
135        
136        if max: sample_rate = self.max_sample_rate()
137        if special_clock: self.special_clock(1)
138        if sample_rate:
139            if isinstance(sample_rate, units.Quantity) and sample_rate.check("[]"):
140                max_sr = self.max_sample_rate()
141                sample_rate = sample_rate.to_base_units().magnitude * max_sr
142            sample_rate = UnitConversion.convert(sample_rate, units.Hz, int)
143            self.card.set_i(SPC_SAMPLERATE, int(sample_rate))
144        return_value = self.card.get_i(SPC_SAMPLERATE)
145        if special_clock and auto_adjust: self.auto_adjust()
146        if return_unit is not None: return_value = UnitConversion.to_unit(return_value * units.Hz, return_unit)
147        return return_value

Sets or gets the current sample rate of the handled card (see register SPC_SAMPLERATE in the manual)

Parameters
  • sample_rate (int | pint.Quantity = 0): if the parameter sample_rate is given with the function call, then the card's sample rate is set to that value
  • max (bool = False): if max is True, the method sets the maximum sample rate of the card
  • special_clock (bool = False): if special_clock is True, the method sets the special clock mode of the card
  • auto_adjust (bool = False): if auto_adjust is True, the method automatically calibrates the values of the ADC at this specific special clock sampling rate
  • unit (pint.Unit = None): the unit of the sample rate, by default None
Returns
  • int: the current sample rate in Samples/s
def oversampling_factor(self) -> int:
149    def oversampling_factor(self) -> int:
150        """
151        Returns the oversampling factor of the card (see register `SPC_OVERSAMPLINGFACTOR` in the manual)
152        
153        Returns
154        -------
155        int
156            the oversampling factor of the card
157        """
158        
159        return self.card.get_i(SPC_OVERSAMPLINGFACTOR)

Returns the oversampling factor of the card (see register SPC_OVERSAMPLINGFACTOR in the manual)

Returns
  • int: the oversampling factor of the card
def clock_output(self, clock_output: int = None) -> int:
161    def clock_output(self, clock_output : int = None) -> int:
162        """
163        Set the clock output of the card (see register `SPC_CLOCKOUT` in the manual)
164        
165        Parameters
166        ----------
167        clock_output : int
168            the clock output of the card
169        
170        Returns
171        -------
172        int
173            the clock output of the card
174        """
175        
176        if clock_output is not None:
177            self.card.set_i(SPC_CLOCKOUT, int(clock_output))
178        return self.card.get_i(SPC_CLOCKOUT)

Set the clock output of the card (see register SPC_CLOCKOUT in the manual)

Parameters
  • clock_output (int): the clock output of the card
Returns
  • int: the clock output of the card
def output(self, clock_output: int = None) -> int:
161    def clock_output(self, clock_output : int = None) -> int:
162        """
163        Set the clock output of the card (see register `SPC_CLOCKOUT` in the manual)
164        
165        Parameters
166        ----------
167        clock_output : int
168            the clock output of the card
169        
170        Returns
171        -------
172        int
173            the clock output of the card
174        """
175        
176        if clock_output is not None:
177            self.card.set_i(SPC_CLOCKOUT, int(clock_output))
178        return self.card.get_i(SPC_CLOCKOUT)

Set the clock output of the card (see register SPC_CLOCKOUT in the manual)

Parameters
  • clock_output (int): the clock output of the card
Returns
  • int: the clock output of the card
def clock_output_frequency(self, return_unit=None) -> int:
181    def clock_output_frequency(self, return_unit = None) -> int:
182        """
183        Returns the clock output frequency of the card (see register `SPC_CLOCKOUTFREQUENCY` in the manual)
184        
185        Parameters
186        ----------
187        return_unit : pint.Unit = None
188            the unit of the clock output frequency
189        
190        Returns
191        -------
192        int | pint.Quantity
193            the clock output frequency of the card
194        """
195        
196        value = self.card.get_i(SPC_CLOCKOUTFREQUENCY)
197        value = UnitConversion.to_unit(value * units.Hz, return_unit)
198        return value

Returns the clock output frequency of the card (see register SPC_CLOCKOUTFREQUENCY in the manual)

Parameters
  • return_unit (pint.Unit = None): the unit of the clock output frequency
Returns
  • int | pint.Quantity: the clock output frequency of the card
def reference_clock(self, reference_clock: int = None) -> int:
200    def reference_clock(self, reference_clock : int = None) -> int:
201        """
202        Set the reference clock of the card (see register `SPC_REFERENCECLOCK` in the manual)
203        
204        Parameters
205        ----------
206        reference_clock : int | pint.Quantity
207            the reference clock of the card in Hz
208        
209        Returns
210        -------
211        int
212            the reference clock of the card in Hz
213        """
214        
215        if reference_clock is not None:
216            reference_clock = UnitConversion.convert(reference_clock, units.Hz, int)
217            self.card.set_i(SPC_REFERENCECLOCK, reference_clock)
218        return self.card.get_i(SPC_REFERENCECLOCK)

Set the reference clock of the card (see register SPC_REFERENCECLOCK in the manual)

Parameters
  • reference_clock (int | pint.Quantity): the reference clock of the card in Hz
Returns
  • int: the reference clock of the card in Hz
def termination(self, termination: int = None) -> int:
220    def termination(self, termination : int = None) -> int:
221        """
222        Set the termination for the clock input of the card (see register `SPC_CLOCK50OHM` in the manual)
223        
224        Parameters
225        ----------
226        termination : int | bool
227            the termination of the card
228        
229        Returns
230        -------
231        int
232            the termination of the card
233        """
234        
235        if termination is not None:
236            self.card.set_i(SPC_CLOCK50OHM, int(termination))
237        return self.card.get_i(SPC_CLOCK50OHM)

Set the termination for the clock input of the card (see register SPC_CLOCK50OHM in the manual)

Parameters
  • termination (int | bool): the termination of the card
Returns
  • int: the termination of the card
def threshold(self, value: int = None, return_unit=None) -> int:
239    def threshold(self, value : int = None, return_unit = None) -> int:
240        """
241        Set the clock threshold of the card (see register `SPC_CLOCKTHRESHOLD` in the manual)
242        
243        Parameters
244        ----------
245        value : int
246            the clock threshold of the card
247        return_unit : pint.Unit = None
248            the unit of the clock threshold
249        
250        Returns
251        -------
252        int | pint.Quantity
253            the clock threshold of the card
254        """
255        
256        if value is not None:
257            value = UnitConversion.convert(value, units.mV, int)
258            self.card.set_i(SPC_CLOCK_THRESHOLD, int(value))
259        value = self.card.get_i(SPC_CLOCK_THRESHOLD)
260        value = UnitConversion.to_unit(value * units.mV, return_unit)
261        return value

Set the clock threshold of the card (see register SPC_CLOCKTHRESHOLD in the manual)

Parameters
  • value (int): the clock threshold of the card
  • return_unit (pint.Unit = None): the unit of the clock threshold
Returns
  • int | pint.Quantity: the clock threshold of the card
def threshold_min(self, return_unit=None) -> int:
263    def threshold_min(self, return_unit = None) -> int:
264        """
265        Returns the minimum clock threshold of the card (see register `SPC_CLOCK_AVAILTHRESHOLD_MIN` in the manual)
266
267        Parameters
268        ----------
269        return_unit : pint.Unit = None
270            the unit of the return clock threshold
271        
272        Returns
273        -------
274        int
275            the minimum clock threshold of the card
276        """
277        
278        value = self.card.get_i(SPC_CLOCK_AVAILTHRESHOLD_MIN)
279        value = UnitConversion.to_unit(value * units.mV, return_unit)
280        return value

Returns the minimum clock threshold of the card (see register SPC_CLOCK_AVAILTHRESHOLD_MIN in the manual)

Parameters
  • return_unit (pint.Unit = None): the unit of the return clock threshold
Returns
  • int: the minimum clock threshold of the card
def threshold_max(self, return_unit=None) -> int:
282    def threshold_max(self, return_unit = None) -> int:
283        """
284        Returns the maximum clock threshold of the card (see register `SPC_CLOCK_AVAILTHRESHOLD_MAX` in the manual)
285
286        Parameters
287        ----------
288        return_unit : pint.Unit = None
289            the unit of the return clock threshold
290        
291        Returns
292        -------
293        int
294            the maximum clock threshold of the card
295        """
296        
297        value = self.card.get_i(SPC_CLOCK_AVAILTHRESHOLD_MAX)
298        value = UnitConversion.to_unit(value * units.mV, return_unit)
299        return value

Returns the maximum clock threshold of the card (see register SPC_CLOCK_AVAILTHRESHOLD_MAX in the manual)

Parameters
  • return_unit (pint.Unit = None): the unit of the return clock threshold
Returns
  • int: the maximum clock threshold of the card
def threshold_step(self, return_unit=None) -> int:
301    def threshold_step(self, return_unit = None) -> int:
302        """
303        Returns the step of the clock threshold of the card (see register `SPC_CLOCK_AVAILTHRESHOLD_STEP` in the manual)
304
305        Parameters
306        ----------
307        return_unit : pint.Unit = None
308            the unit of the return clock threshold
309        
310        Returns
311        -------
312        int
313            the step of the clock threshold of the card
314        """
315        
316        value = self.card.get_i(SPC_CLOCK_AVAILTHRESHOLD_STEP)
317        value = UnitConversion.to_unit(value * units.mV, return_unit)
318        return value

Returns the step of the clock threshold of the card (see register SPC_CLOCK_AVAILTHRESHOLD_STEP in the manual)

Parameters
  • return_unit (pint.Unit = None): the unit of the return clock threshold
Returns
  • int: the step of the clock threshold of the card
def edge(self, edge: int = None) -> int:
320    def edge(self, edge : int = None) -> int:
321        """
322        Set the clock edge of the card (see register `SPC_CLOCK_EDGE` in the manual)
323        
324        Parameters
325        ----------
326        edge : int
327            the clock edge of the card
328        
329        Returns
330        -------
331        int
332            the clock edge of the card
333        """
334        
335        if edge is not None:
336            self.card.set_i(SPC_CLOCK_EDGE, int(edge))
337        return self.card.get_i(SPC_CLOCK_EDGE)

Set the clock edge of the card (see register SPC_CLOCK_EDGE in the manual)

Parameters
  • edge (int): the clock edge of the card
Returns
  • int: the clock edge of the card
def delay(self, delay: int = None, return_unit=None):
339    def delay(self, delay : int = None, return_unit = None):
340        """
341        Set the clock delay of the card (see register `SPC_CLOCK_DELAY` in the manual)
342
343        Parameters
344        ----------
345        delay : int | pint.Quantity | pint.Unit = None
346            the clock delay of the card
347        return_unit : pint.Unit = None
348            the unit of the clock delay
349
350        Returns
351        -------
352        int | pint.Unit
353            the clock delay of the card
354        """
355
356        if delay is not None:
357            delay = UnitConversion.convert(delay, units.ps, int)
358            self.card.set_i(SPC_CLOCK_DELAY, int(delay))
359        delay = self.card.get_i(SPC_CLOCK_DELAY)
360        delay = UnitConversion.to_unit(delay * units.ps, return_unit)
361        return delay

Set the clock delay of the card (see register SPC_CLOCK_DELAY in the manual)

Parameters
  • delay (int | pint.Quantity | pint.Unit = None): the clock delay of the card
  • return_unit (pint.Unit = None): the unit of the clock delay
Returns
  • int | pint.Unit: the clock delay of the card
def delay_min(self, return_unit=None) -> int:
363    def delay_min(self, return_unit = None) -> int:
364        """
365        Returns the minimum clock delay of the card (see register `SPC_CLOCK_AVAILDELAY_MIN` in the manual)
366
367        Parameters
368        ----------
369        return_unit : pint.Unit = None
370            the unit of the return clock delay
371
372        Returns
373        -------
374        int | pint.Quantity
375            the minimum clock delay of the card
376        """
377        
378        value = self.card.get_i(SPC_CLOCK_AVAILDELAY_MIN)
379        value = UnitConversion.to_unit(value * units.ps, return_unit)
380        return value

Returns the minimum clock delay of the card (see register SPC_CLOCK_AVAILDELAY_MIN in the manual)

Parameters
  • return_unit (pint.Unit = None): the unit of the return clock delay
Returns
  • int | pint.Quantity: the minimum clock delay of the card
def delay_max(self, return_unit=None) -> int:
382    def delay_max(self, return_unit = None) -> int:
383        """
384        Returns the maximum clock delay of the card (see register `SPC_CLOCK_AVAILDELAY_MAX` in the manual)
385
386        Parameters
387        ----------
388        return_unit : pint.Unit = None
389            the unit of the return clock delay
390
391        Returns
392        -------
393        int | pint.Quantity
394            the maximum clock delay of the card
395        """
396        
397        value = self.card.get_i(SPC_CLOCK_AVAILDELAY_MAX)
398        value = UnitConversion.to_unit(value * units.ps, return_unit)
399        return value

Returns the maximum clock delay of the card (see register SPC_CLOCK_AVAILDELAY_MAX in the manual)

Parameters
  • return_unit (pint.Unit = None): the unit of the return clock delay
Returns
  • int | pint.Quantity: the maximum clock delay of the card
def delay_step(self, return_unit=None) -> int:
401    def delay_step(self, return_unit = None) -> int:
402        """
403        Returns the step of the clock delay of the card (see register `SPC_CLOCK_AVAILDELAY_STEP` in the manual)
404
405        Parameters
406        ----------
407        return_unit : pint.Unit = None
408            the unit of the return clock delay
409
410        Returns
411        -------
412        int | pint.Quantity
413            the step of the clock delay of the card
414        """
415        
416        value = self.card.get_i(SPC_CLOCK_AVAILDELAY_STEP)
417        value = UnitConversion.to_unit(value * units.ps, return_unit)
418        return value

Returns the step of the clock delay of the card (see register SPC_CLOCK_AVAILDELAY_STEP in the manual)

Parameters
  • return_unit (pint.Unit = None): the unit of the return clock delay
Returns
  • int | pint.Quantity: the step of the clock delay of the card
class Trigger(spcm.CardFunctionality):
 16class Trigger(CardFunctionality):
 17    """a higher-level abstraction of the CardFunctionality class to implement the Card's Trigger engine"""
 18
 19    channels : Channels = None
 20
 21    def __init__(self, card : 'Card', **kwargs) -> None:
 22        """
 23        Constructor of the Trigger class
 24        
 25        Parameters
 26        ----------
 27        card : Card
 28            The card to use for the Trigger class
 29        """
 30
 31        super().__init__(card)
 32        self.channels = kwargs.get('channels', None)
 33        self.clock = kwargs.get('clock', None)
 34    
 35    def __str__(self) -> str:
 36        """
 37        String representation of the Trigger class
 38    
 39        Returns
 40        -------
 41        str
 42            String representation of the Trigger class
 43        """
 44        
 45        return f"Trigger(card={self.card})"
 46    
 47    __repr__ = __str__
 48
 49    def enable(self) -> None:
 50        """Enables the trigger engine (see command 'M2CMD_CARD_ENABLETRIGGER' in chapter `Trigger` in the manual)"""
 51        self.card.cmd(M2CMD_CARD_ENABLETRIGGER)
 52    
 53    def disable(self) -> None:
 54        """Disables the trigger engine (see command 'M2CMD_CARD_DISABLETRIGGER' in chapter `Trigger` in the manual)"""
 55        self.card.cmd(M2CMD_CARD_DISABLETRIGGER)
 56    
 57    def force(self) -> None:
 58        """Forces a trigger event if the hardware is still waiting for a trigger event. (see command 'M2CMD_CARD_FORCETRIGGER' in chapter `Trigger` in the manual)"""
 59        self.card.cmd(M2CMD_CARD_FORCETRIGGER)
 60    
 61    def write_setup(self) -> None:
 62        """Write the trigger setup to the card"""
 63        self.card.write_setup()
 64    
 65    # OR Mask
 66    def or_mask(self, mask : int = None) -> int:
 67        """
 68        Set the OR mask for the trigger input lines (see register 'SPC_TRIG_ORMASK' in chapter `Trigger` in the manual)
 69        
 70        Parameters
 71        ----------
 72        mask : int
 73            The OR mask for the trigger input lines
 74        
 75        Returns
 76        -------
 77        int
 78            The OR mask for the trigger input lines
 79        """
 80
 81        if mask is not None:
 82            self.card.set_i(SPC_TRIG_ORMASK, mask)
 83        return self.card.get_i(SPC_TRIG_ORMASK)
 84
 85    # AND Mask
 86    def and_mask(self, mask : int = None) -> int:
 87        """
 88        Set the AND mask for the trigger input lines (see register 'SPC_TRIG_ANDMASK' in chapter `Trigger` in the manual)
 89        
 90        Parameters
 91        ----------
 92        mask : int
 93            The AND mask for the trigger input lines
 94        
 95        Returns
 96        -------
 97        int
 98            The AND mask for the trigger input lines
 99        """
100
101        if mask is not None:
102            self.card.set_i(SPC_TRIG_ANDMASK, mask)
103        return self.card.get_i(SPC_TRIG_ANDMASK)
104
105    # Channel triggering
106    def ch_mode(self, channel, mode : int = None) -> int:
107        """
108        Set the mode for the trigger input lines (see register 'SPC_TRIG_CH0_MODE' in chapter `Trigger` in the manual)
109        
110        Parameters
111        ----------
112        channel : int | Channel
113            The channel to set the mode for
114        mode : int
115            The mode for the trigger input lines
116        
117        Returns
118        -------
119        int
120            The mode for the trigger input lines
121        
122        """
123
124        channel_index = int(channel)
125        if mode is not None:
126            self.card.set_i(SPC_TRIG_CH0_MODE + channel_index, mode)
127        return self.card.get_i(SPC_TRIG_CH0_MODE + channel_index)
128
129    def ch_level(self, channel : int, level_num : int, level_value = None, return_unit : pint.Unit = None) -> int:
130        """
131        Set the level for the trigger input lines (see register 'SPC_TRIG_CH0_LEVEL0' in chapter `Trigger` in the manual)
132        
133        Parameters
134        ----------
135        channel : int | Channel
136            The channel to set the level for
137        level_num : int
138            The level 0 or level 1
139        level_value : int | pint.Quantity | None
140            The level for the trigger input lines
141        
142        Returns
143        -------
144        int
145            The level for the trigger input lines
146        """
147
148        channel_index = int(channel)
149        # if a level value is given in the form of a quantity, convert it to the card's unit as a integer value
150        if isinstance(level_value, units.Quantity):
151            if isinstance(channel, Channel):
152                level_value = channel.reconvert_data(level_value)
153            elif self.channels and isinstance(self.channels[channel_index], Channel):
154                level_value = self.channels[channel_index].reconvert_data(level_value)
155            else:
156                raise ValueError("No channel information available to convert the trigger level value. Please provide a channel object or set the channel information in the Trigger object.")
157        
158        if isinstance(level_value, int):
159            self.card.set_i(SPC_TRIG_CH0_LEVEL0 + channel_index + 100 * level_num, level_value)
160
161        return_value = self.card.get_i(SPC_TRIG_CH0_LEVEL0 + channel_index + 100 * level_num)
162        # if a return unit is given, convert the value to the given unit if a channel object is available
163        if isinstance(return_unit, pint.Unit):
164            if isinstance(channel, Channel):
165                return_value = channel.convert_data(return_value, return_unit=return_unit)
166            elif self.channels and isinstance(self.channels[channel_index], Channel):
167                return_value = self.channels[channel_index].convert_data(return_value, return_unit=return_unit)
168            else:
169                raise ValueError("No channel information available to convert the returning trigger level value. Please provide a channel object or set the channel information in the Trigger object.")
170            
171        return return_value
172
173    def ch_level0(self, channel : int, level_value = None, return_unit : pint.Unit = None) -> int:
174        """
175        Set the level 0 for the trigger input lines (see register 'SPC_TRIG_CH0_LEVEL0' in chapter `Trigger` in the manual)
176        
177        Parameters
178        ----------
179        channel : int | Channel
180            The channel to set the level for
181        level_value : int | pint.Quantity | None
182            The level for the trigger input lines
183        
184        Returns
185        -------
186        int
187            The level for the trigger input lines
188        """
189
190        return self.ch_level(channel, 0, level_value, return_unit)
191    
192    def ch_level1(self, channel : int, level_value = None, return_unit : pint.Unit = None) -> int:
193        """
194        Set the level 1 for the trigger input lines (see register 'SPC_TRIG_CH0_LEVEL1' in chapter `Trigger` in the manual)
195        
196        Parameters
197        ----------
198        channel : int | Channel
199            The channel to set the level for
200        level_value : int | pint.Quantity | None
201            The level for the trigger input lines
202        
203        Returns
204        -------
205        int
206            The level for the trigger input lines
207        """
208
209        return self.ch_level(channel, 1, level_value, return_unit)
210    
211    def ch_pulsewidth(self, channel : int, pulsewidth_value = None, return_unit : pint.Unit = None) -> int:
212        """
213        Set the pulse width for the trigger input lines (see register 'SPC_TRIG_CH0_PULSEWIDTH' in chapter `Trigger` in the manual)
214        
215        Parameters
216        ----------
217        channel : int | Channel
218            The channel to set the pulse width for
219        pulsewidth_value : int | pint.Quantity | None
220            The pulse width for the trigger input lines
221        
222        Returns
223        -------
224        int
225            The pulse width for the trigger input lines
226        """
227
228        channel_index = int(channel)
229        if pulsewidth_value is not None:
230            if isinstance(pulsewidth_value, units.Quantity) and pulsewidth_value.check("[time]"):
231                if self.clock is None:
232                    raise ValueError("No clock information available to convert the trigger pulse width value. Please provide a clock object to the Trigger object.")
233                sample_rate = self.clock.sample_rate(return_unit=units.Hz)
234                pulsewidth_value = np.rint((pulsewidth_value * sample_rate).to_base_units().magnitude).astype(np.int64)
235            self.card.set_i(SPC_TRIG_CH0_PULSEWIDTH + channel_index, pulsewidth_value)
236        
237        return_value = self.card.get_i(SPC_TRIG_CH0_PULSEWIDTH + channel_index)
238        # if a return unit is given, convert the value to the given unit if a channel object is available
239        if isinstance(return_unit, pint.Unit):
240            if self.clock is None:
241                raise ValueError("No clock information available to convert the trigger pulse width value. Please provide a clock object to the Trigger object.")
242            sample_rate = self.clock.sample_rate(return_unit=units.Hz)
243            return_value = UnitConversion.to_unit(return_value / sample_rate, return_unit)
244            
245        return return_value
246
247    # Channel OR Mask0
248    def ch_or_mask0(self, mask : int = None) -> int:
249        """
250        Set the channel OR mask0 for the trigger input lines (see register 'SPC_TRIG_CH_ORMASK0' in chapter `Trigger` in the manual)
251        
252        Parameters
253        ----------
254        mask : int
255            The OR mask for the trigger input lines
256        
257        Returns
258        -------
259        int
260            The OR mask for the trigger input lines
261        """
262
263        if mask is not None:
264            self.card.set_i(SPC_TRIG_CH_ORMASK0, mask)
265        return self.card.get_i(SPC_TRIG_CH_ORMASK0)
266    
267    # Channel AND Mask0
268    def ch_and_mask0(self, mask : int = None) -> int:
269        """
270        Set the AND mask0 for the trigger input lines (see register 'SPC_TRIG_CH_ANDMASK0' in chapter `Trigger` in the manual)
271        
272        Parameters
273        ----------
274        mask : int
275            The AND mask0 for the trigger input lines
276        
277        Returns
278        -------
279        int
280            The AND mask0 for the trigger input lines
281        """
282
283        if mask is not None:
284            self.card.set_i(SPC_TRIG_CH_ANDMASK0, mask)
285        return self.card.get_i(SPC_TRIG_CH_ANDMASK0)
286    
287    # Delay
288    def delay(self, delay = None, return_unit : pint.Unit = None) -> int:
289        """
290        Set the delay for the trigger input lines in number of sample clocks (see register 'SPC_TRIG_DELAY' in chapter `Trigger` in the manual)
291        
292        Parameters
293        ----------
294        delay : int | pint.Quantity
295            The delay for the trigger input lines
296        return_unit : pint.Unit
297            The unit to return the value in
298
299        Returns
300        -------
301        int | pint.Quantity
302            The delay for the trigger input lines
303
304        NOTE
305        ----
306        different cards have different step sizes for the delay. 
307        If a delay with unit is given, this function takes the value, 
308        calculates the integer value and rounds to the nearest allowed delay value
309        """
310
311        sr = self.card.get_i(SPC_SAMPLERATE) * units.Hz
312        if delay is not None:
313            if isinstance(delay, units.Quantity):
314                delay_step = self.avail_delay_step()
315                delay = np.rint(int(delay * sr) / delay_step).astype(np.int64) * delay_step
316            self.card.set_i(SPC_TRIG_DELAY, delay)
317        return_value = self.card.get_i(SPC_TRIG_DELAY)
318        if isinstance(return_unit, pint.Unit): return_value = UnitConversion.to_unit(return_value / sr, return_unit)
319        return return_value
320    
321    def avail_delay_max(self) -> int:
322        """
323        Get the maximum delay for the trigger input lines in number of sample clocks (see register 'SPC_TRIG_AVAILDELAY' in chapter `Trigger` in the manual)
324        
325        Returns
326        -------
327        int
328            The maximum delay for the trigger input lines
329        """
330
331        return self.card.get_i(SPC_TRIG_AVAILDELAY)
332    
333    def avail_delay_step(self) -> int:
334        """
335        Get the step size for the delay for the trigger input lines in number of sample clocks (see register 'SPC_TRIG_AVAILDELAY_STEP' in chapter `Trigger` in the manual)
336        
337        Returns
338        -------
339        int
340            The step size for the delay for the trigger input lines
341        """
342
343        return self.card.get_i(SPC_TRIG_AVAILDELAY_STEP)
344
345    
346    def trigger_counter(self) -> int:
347        """
348        Get the number of trigger events since acquisition start (see register 'SPC_TRIGGERCOUNTER' in chapter `Trigger` in the manual)
349        
350        Returns
351        -------
352        int
353            The trigger counter
354        """
355
356        return self.card.get_i(SPC_TRIGGERCOUNTER)
357    
358    # Main external window trigger (ext0/Trg0)
359    def ext0_mode(self, mode : int = None) -> int:
360        """
361        Set the mode for the main external window trigger (ext0/Trg0) (see register 'SPC_TRIG_EXT0_MODE' in chapter `Trigger` in the manual)
362        
363        Parameters
364        ----------
365        mode : int
366            The mode for the main external window trigger (ext0/Trg0)
367        
368        Returns
369        -------
370        int
371            The mode for the main external window trigger (ext0/Trg0)
372        """
373
374        if mode is not None:
375            self.card.set_i(SPC_TRIG_EXT0_MODE, mode)
376        return self.card.get_i(SPC_TRIG_EXT0_MODE)
377    
378    # Trigger termination
379    def termination(self, termination : int = None) -> int:
380        """
381        Set the trigger termination (see register 'SPC_TRIG_TERM' in chapter `Trigger` in the manual)
382        
383        Parameters
384        ----------
385        termination : int
386            The trigger termination: a „1“ sets the 50 Ohm termination for external trigger signals. A „0“ sets the high impedance termination
387        
388        Returns
389        -------
390        int
391            The trigger termination: a „1“ sets the 50 Ohm termination for external trigger signals. A „0“ sets the high impedance termination
392        """
393
394        if termination is not None:
395            self.card.set_i(SPC_TRIG_TERM, termination)
396        return self.card.get_i(SPC_TRIG_TERM)
397    
398    # Trigger input coupling
399    def ext0_coupling(self, coupling : int = None) -> int:
400        """
401        Set the trigger input coupling (see hardware manual register name 'SPC_TRIG_EXT0_ACDC')
402        
403        Parameters
404        ----------
405        coupling : int
406            The trigger input coupling: COUPLING_DC enables DC coupling, COUPLING_AC enables AC coupling for the external trigger 
407            input (AC coupling is the default).
408
409        Returns
410        -------
411        int
412            The trigger input coupling: COUPLING_DC enables DC coupling, COUPLING_AC enables AC coupling for the external trigger 
413            input (AC coupling is the default).
414        """
415
416        if coupling is not None:
417            self.card.set_i(SPC_TRIG_EXT0_ACDC, coupling)
418        return self.card.get_i(SPC_TRIG_EXT0_ACDC)
419    
420    # ext1 trigger mode
421    def ext1_mode(self, mode : int = None) -> int:
422        """
423        Set the mode for the ext1 trigger (see register 'SPC_TRIG_EXT1_MODE' in chapter `Trigger` in the manual)
424        
425        Parameters
426        ----------
427        mode : int
428            The mode for the ext1 trigger
429        
430        Returns
431        -------
432        int
433            The mode for the ext1 trigger
434        """
435
436        if mode is not None:
437            self.card.set_i(SPC_TRIG_EXT1_MODE, mode)
438        return self.card.get_i(SPC_TRIG_EXT1_MODE)
439    
440    # Trigger level
441    def ext0_level0(self, level = None, return_unit = None) -> int:
442        """
443        Set the trigger level 0 for the ext0 trigger (see register 'SPC_TRIG_EXT0_LEVEL0' in chapter `Trigger` in the manual)
444        
445        Parameters
446        ----------
447        level : int
448            The trigger level 0 for the ext0 trigger in mV
449        return_unit : pint.Unit
450            The unit to return the value in
451        
452        Returns
453        -------
454        int | pint.Quantity
455            The trigger level 0 for the ext0 trigger in mV or in the specified unit
456        """
457
458        if level is not None:
459            level = UnitConversion.convert(level, units.mV, int)
460            self.card.set_i(SPC_TRIG_EXT0_LEVEL0, level)
461        return_value = self.card.get_i(SPC_TRIG_EXT0_LEVEL0)
462        return UnitConversion.to_unit(return_value * units.mV, return_unit)
463    
464    def ext0_level1(self, level = None, return_unit = None) -> int:
465        """
466        Set the trigger level 1 for the ext0 trigger (see register 'SPC_TRIG_EXT0_LEVEL1' in chapter `Trigger` in the manual)
467        
468        Parameters
469        ----------
470        level : int
471            The trigger level for the ext0 trigger in mV
472        return_unit : pint.Unit
473            The unit to return the value in
474        
475        Returns
476        -------
477        int | pint.Quantity
478            The trigger level for the ext0 trigger in mV or in the specified unit
479        """
480
481        if level is not None:
482            level = UnitConversion.convert(level, units.mV, int)
483            self.card.set_i(SPC_TRIG_EXT0_LEVEL1, level)
484        return_value = self.card.get_i(SPC_TRIG_EXT0_LEVEL1)
485        return UnitConversion.to_unit(return_value * units.mV, return_unit)
486    
487    def ext1_level0(self, level = None, return_unit = None) -> int:
488        """
489        Set the trigger level 0 for the ext1 trigger (see register 'SPC_TRIG_EXT1_LEVEL0' in chapter `Trigger` in the manual)
490        
491        Parameters
492        ----------
493        level : int
494            The trigger level 0 for the ext1 trigger in mV
495        return_unit : pint.Unit
496            The unit to return the value in
497        
498        Returns
499        -------
500        int | pint.Quantity
501            The trigger level 0 for the ext1 trigger in mV or in the specified unit
502        """
503
504        if level is not None:
505            level = UnitConversion.convert(level, units.mV, int)
506            self.card.set_i(SPC_TRIG_EXT1_LEVEL0, level)
507        return_value = self.card.get_i(SPC_TRIG_EXT1_LEVEL0)
508        return UnitConversion.to_unit(return_value * units.mV, return_unit)

a higher-level abstraction of the CardFunctionality class to implement the Card's Trigger engine

Trigger(card: Card, **kwargs)
21    def __init__(self, card : 'Card', **kwargs) -> None:
22        """
23        Constructor of the Trigger class
24        
25        Parameters
26        ----------
27        card : Card
28            The card to use for the Trigger class
29        """
30
31        super().__init__(card)
32        self.channels = kwargs.get('channels', None)
33        self.clock = kwargs.get('clock', None)

Constructor of the Trigger class

Parameters
  • card (Card): The card to use for the Trigger class
channels: Channels = None
clock
def enable(self) -> None:
49    def enable(self) -> None:
50        """Enables the trigger engine (see command 'M2CMD_CARD_ENABLETRIGGER' in chapter `Trigger` in the manual)"""
51        self.card.cmd(M2CMD_CARD_ENABLETRIGGER)

Enables the trigger engine (see command 'M2CMD_CARD_ENABLETRIGGER' in chapter Trigger in the manual)

def disable(self) -> None:
53    def disable(self) -> None:
54        """Disables the trigger engine (see command 'M2CMD_CARD_DISABLETRIGGER' in chapter `Trigger` in the manual)"""
55        self.card.cmd(M2CMD_CARD_DISABLETRIGGER)

Disables the trigger engine (see command 'M2CMD_CARD_DISABLETRIGGER' in chapter Trigger in the manual)

def force(self) -> None:
57    def force(self) -> None:
58        """Forces a trigger event if the hardware is still waiting for a trigger event. (see command 'M2CMD_CARD_FORCETRIGGER' in chapter `Trigger` in the manual)"""
59        self.card.cmd(M2CMD_CARD_FORCETRIGGER)

Forces a trigger event if the hardware is still waiting for a trigger event. (see command 'M2CMD_CARD_FORCETRIGGER' in chapter Trigger in the manual)

def write_setup(self) -> None:
61    def write_setup(self) -> None:
62        """Write the trigger setup to the card"""
63        self.card.write_setup()

Write the trigger setup to the card

def or_mask(self, mask: int = None) -> int:
66    def or_mask(self, mask : int = None) -> int:
67        """
68        Set the OR mask for the trigger input lines (see register 'SPC_TRIG_ORMASK' in chapter `Trigger` in the manual)
69        
70        Parameters
71        ----------
72        mask : int
73            The OR mask for the trigger input lines
74        
75        Returns
76        -------
77        int
78            The OR mask for the trigger input lines
79        """
80
81        if mask is not None:
82            self.card.set_i(SPC_TRIG_ORMASK, mask)
83        return self.card.get_i(SPC_TRIG_ORMASK)

Set the OR mask for the trigger input lines (see register 'SPC_TRIG_ORMASK' in chapter Trigger in the manual)

Parameters
  • mask (int): The OR mask for the trigger input lines
Returns
  • int: The OR mask for the trigger input lines
def and_mask(self, mask: int = None) -> int:
 86    def and_mask(self, mask : int = None) -> int:
 87        """
 88        Set the AND mask for the trigger input lines (see register 'SPC_TRIG_ANDMASK' in chapter `Trigger` in the manual)
 89        
 90        Parameters
 91        ----------
 92        mask : int
 93            The AND mask for the trigger input lines
 94        
 95        Returns
 96        -------
 97        int
 98            The AND mask for the trigger input lines
 99        """
100
101        if mask is not None:
102            self.card.set_i(SPC_TRIG_ANDMASK, mask)
103        return self.card.get_i(SPC_TRIG_ANDMASK)

Set the AND mask for the trigger input lines (see register 'SPC_TRIG_ANDMASK' in chapter Trigger in the manual)

Parameters
  • mask (int): The AND mask for the trigger input lines
Returns
  • int: The AND mask for the trigger input lines
def ch_mode(self, channel, mode: int = None) -> int:
106    def ch_mode(self, channel, mode : int = None) -> int:
107        """
108        Set the mode for the trigger input lines (see register 'SPC_TRIG_CH0_MODE' in chapter `Trigger` in the manual)
109        
110        Parameters
111        ----------
112        channel : int | Channel
113            The channel to set the mode for
114        mode : int
115            The mode for the trigger input lines
116        
117        Returns
118        -------
119        int
120            The mode for the trigger input lines
121        
122        """
123
124        channel_index = int(channel)
125        if mode is not None:
126            self.card.set_i(SPC_TRIG_CH0_MODE + channel_index, mode)
127        return self.card.get_i(SPC_TRIG_CH0_MODE + channel_index)

Set the mode for the trigger input lines (see register 'SPC_TRIG_CH0_MODE' in chapter Trigger in the manual)

Parameters
  • channel (int | Channel): The channel to set the mode for
  • mode (int): The mode for the trigger input lines
Returns
  • int: The mode for the trigger input lines
def ch_level( self, channel: int, level_num: int, level_value=None, return_unit: pint.registry.Unit = None) -> int:
129    def ch_level(self, channel : int, level_num : int, level_value = None, return_unit : pint.Unit = None) -> int:
130        """
131        Set the level for the trigger input lines (see register 'SPC_TRIG_CH0_LEVEL0' in chapter `Trigger` in the manual)
132        
133        Parameters
134        ----------
135        channel : int | Channel
136            The channel to set the level for
137        level_num : int
138            The level 0 or level 1
139        level_value : int | pint.Quantity | None
140            The level for the trigger input lines
141        
142        Returns
143        -------
144        int
145            The level for the trigger input lines
146        """
147
148        channel_index = int(channel)
149        # if a level value is given in the form of a quantity, convert it to the card's unit as a integer value
150        if isinstance(level_value, units.Quantity):
151            if isinstance(channel, Channel):
152                level_value = channel.reconvert_data(level_value)
153            elif self.channels and isinstance(self.channels[channel_index], Channel):
154                level_value = self.channels[channel_index].reconvert_data(level_value)
155            else:
156                raise ValueError("No channel information available to convert the trigger level value. Please provide a channel object or set the channel information in the Trigger object.")
157        
158        if isinstance(level_value, int):
159            self.card.set_i(SPC_TRIG_CH0_LEVEL0 + channel_index + 100 * level_num, level_value)
160
161        return_value = self.card.get_i(SPC_TRIG_CH0_LEVEL0 + channel_index + 100 * level_num)
162        # if a return unit is given, convert the value to the given unit if a channel object is available
163        if isinstance(return_unit, pint.Unit):
164            if isinstance(channel, Channel):
165                return_value = channel.convert_data(return_value, return_unit=return_unit)
166            elif self.channels and isinstance(self.channels[channel_index], Channel):
167                return_value = self.channels[channel_index].convert_data(return_value, return_unit=return_unit)
168            else:
169                raise ValueError("No channel information available to convert the returning trigger level value. Please provide a channel object or set the channel information in the Trigger object.")
170            
171        return return_value

Set the level for the trigger input lines (see register 'SPC_TRIG_CH0_LEVEL0' in chapter Trigger in the manual)

Parameters
  • channel (int | Channel): The channel to set the level for
  • level_num (int): The level 0 or level 1
  • level_value (int | pint.Quantity | None): The level for the trigger input lines
Returns
  • int: The level for the trigger input lines
def ch_level0( self, channel: int, level_value=None, return_unit: pint.registry.Unit = None) -> int:
173    def ch_level0(self, channel : int, level_value = None, return_unit : pint.Unit = None) -> int:
174        """
175        Set the level 0 for the trigger input lines (see register 'SPC_TRIG_CH0_LEVEL0' in chapter `Trigger` in the manual)
176        
177        Parameters
178        ----------
179        channel : int | Channel
180            The channel to set the level for
181        level_value : int | pint.Quantity | None
182            The level for the trigger input lines
183        
184        Returns
185        -------
186        int
187            The level for the trigger input lines
188        """
189
190        return self.ch_level(channel, 0, level_value, return_unit)

Set the level 0 for the trigger input lines (see register 'SPC_TRIG_CH0_LEVEL0' in chapter Trigger in the manual)

Parameters
  • channel (int | Channel): The channel to set the level for
  • level_value (int | pint.Quantity | None): The level for the trigger input lines
Returns
  • int: The level for the trigger input lines
def ch_level1( self, channel: int, level_value=None, return_unit: pint.registry.Unit = None) -> int:
192    def ch_level1(self, channel : int, level_value = None, return_unit : pint.Unit = None) -> int:
193        """
194        Set the level 1 for the trigger input lines (see register 'SPC_TRIG_CH0_LEVEL1' in chapter `Trigger` in the manual)
195        
196        Parameters
197        ----------
198        channel : int | Channel
199            The channel to set the level for
200        level_value : int | pint.Quantity | None
201            The level for the trigger input lines
202        
203        Returns
204        -------
205        int
206            The level for the trigger input lines
207        """
208
209        return self.ch_level(channel, 1, level_value, return_unit)

Set the level 1 for the trigger input lines (see register 'SPC_TRIG_CH0_LEVEL1' in chapter Trigger in the manual)

Parameters
  • channel (int | Channel): The channel to set the level for
  • level_value (int | pint.Quantity | None): The level for the trigger input lines
Returns
  • int: The level for the trigger input lines
def ch_pulsewidth( self, channel: int, pulsewidth_value=None, return_unit: pint.registry.Unit = None) -> int:
211    def ch_pulsewidth(self, channel : int, pulsewidth_value = None, return_unit : pint.Unit = None) -> int:
212        """
213        Set the pulse width for the trigger input lines (see register 'SPC_TRIG_CH0_PULSEWIDTH' in chapter `Trigger` in the manual)
214        
215        Parameters
216        ----------
217        channel : int | Channel
218            The channel to set the pulse width for
219        pulsewidth_value : int | pint.Quantity | None
220            The pulse width for the trigger input lines
221        
222        Returns
223        -------
224        int
225            The pulse width for the trigger input lines
226        """
227
228        channel_index = int(channel)
229        if pulsewidth_value is not None:
230            if isinstance(pulsewidth_value, units.Quantity) and pulsewidth_value.check("[time]"):
231                if self.clock is None:
232                    raise ValueError("No clock information available to convert the trigger pulse width value. Please provide a clock object to the Trigger object.")
233                sample_rate = self.clock.sample_rate(return_unit=units.Hz)
234                pulsewidth_value = np.rint((pulsewidth_value * sample_rate).to_base_units().magnitude).astype(np.int64)
235            self.card.set_i(SPC_TRIG_CH0_PULSEWIDTH + channel_index, pulsewidth_value)
236        
237        return_value = self.card.get_i(SPC_TRIG_CH0_PULSEWIDTH + channel_index)
238        # if a return unit is given, convert the value to the given unit if a channel object is available
239        if isinstance(return_unit, pint.Unit):
240            if self.clock is None:
241                raise ValueError("No clock information available to convert the trigger pulse width value. Please provide a clock object to the Trigger object.")
242            sample_rate = self.clock.sample_rate(return_unit=units.Hz)
243            return_value = UnitConversion.to_unit(return_value / sample_rate, return_unit)
244            
245        return return_value

Set the pulse width for the trigger input lines (see register 'SPC_TRIG_CH0_PULSEWIDTH' in chapter Trigger in the manual)

Parameters
  • channel (int | Channel): The channel to set the pulse width for
  • pulsewidth_value (int | pint.Quantity | None): The pulse width for the trigger input lines
Returns
  • int: The pulse width for the trigger input lines
def ch_or_mask0(self, mask: int = None) -> int:
248    def ch_or_mask0(self, mask : int = None) -> int:
249        """
250        Set the channel OR mask0 for the trigger input lines (see register 'SPC_TRIG_CH_ORMASK0' in chapter `Trigger` in the manual)
251        
252        Parameters
253        ----------
254        mask : int
255            The OR mask for the trigger input lines
256        
257        Returns
258        -------
259        int
260            The OR mask for the trigger input lines
261        """
262
263        if mask is not None:
264            self.card.set_i(SPC_TRIG_CH_ORMASK0, mask)
265        return self.card.get_i(SPC_TRIG_CH_ORMASK0)

Set the channel OR mask0 for the trigger input lines (see register 'SPC_TRIG_CH_ORMASK0' in chapter Trigger in the manual)

Parameters
  • mask (int): The OR mask for the trigger input lines
Returns
  • int: The OR mask for the trigger input lines
def ch_and_mask0(self, mask: int = None) -> int:
268    def ch_and_mask0(self, mask : int = None) -> int:
269        """
270        Set the AND mask0 for the trigger input lines (see register 'SPC_TRIG_CH_ANDMASK0' in chapter `Trigger` in the manual)
271        
272        Parameters
273        ----------
274        mask : int
275            The AND mask0 for the trigger input lines
276        
277        Returns
278        -------
279        int
280            The AND mask0 for the trigger input lines
281        """
282
283        if mask is not None:
284            self.card.set_i(SPC_TRIG_CH_ANDMASK0, mask)
285        return self.card.get_i(SPC_TRIG_CH_ANDMASK0)

Set the AND mask0 for the trigger input lines (see register 'SPC_TRIG_CH_ANDMASK0' in chapter Trigger in the manual)

Parameters
  • mask (int): The AND mask0 for the trigger input lines
Returns
  • int: The AND mask0 for the trigger input lines
def delay(self, delay=None, return_unit: pint.registry.Unit = None) -> int:
288    def delay(self, delay = None, return_unit : pint.Unit = None) -> int:
289        """
290        Set the delay for the trigger input lines in number of sample clocks (see register 'SPC_TRIG_DELAY' in chapter `Trigger` in the manual)
291        
292        Parameters
293        ----------
294        delay : int | pint.Quantity
295            The delay for the trigger input lines
296        return_unit : pint.Unit
297            The unit to return the value in
298
299        Returns
300        -------
301        int | pint.Quantity
302            The delay for the trigger input lines
303
304        NOTE
305        ----
306        different cards have different step sizes for the delay. 
307        If a delay with unit is given, this function takes the value, 
308        calculates the integer value and rounds to the nearest allowed delay value
309        """
310
311        sr = self.card.get_i(SPC_SAMPLERATE) * units.Hz
312        if delay is not None:
313            if isinstance(delay, units.Quantity):
314                delay_step = self.avail_delay_step()
315                delay = np.rint(int(delay * sr) / delay_step).astype(np.int64) * delay_step
316            self.card.set_i(SPC_TRIG_DELAY, delay)
317        return_value = self.card.get_i(SPC_TRIG_DELAY)
318        if isinstance(return_unit, pint.Unit): return_value = UnitConversion.to_unit(return_value / sr, return_unit)
319        return return_value

Set the delay for the trigger input lines in number of sample clocks (see register 'SPC_TRIG_DELAY' in chapter Trigger in the manual)

Parameters
  • delay (int | pint.Quantity): The delay for the trigger input lines
  • return_unit (pint.Unit): The unit to return the value in
Returns
  • int | pint.Quantity: The delay for the trigger input lines
NOTE

different cards have different step sizes for the delay. If a delay with unit is given, this function takes the value, calculates the integer value and rounds to the nearest allowed delay value

def avail_delay_max(self) -> int:
321    def avail_delay_max(self) -> int:
322        """
323        Get the maximum delay for the trigger input lines in number of sample clocks (see register 'SPC_TRIG_AVAILDELAY' in chapter `Trigger` in the manual)
324        
325        Returns
326        -------
327        int
328            The maximum delay for the trigger input lines
329        """
330
331        return self.card.get_i(SPC_TRIG_AVAILDELAY)

Get the maximum delay for the trigger input lines in number of sample clocks (see register 'SPC_TRIG_AVAILDELAY' in chapter Trigger in the manual)

Returns
  • int: The maximum delay for the trigger input lines
def avail_delay_step(self) -> int:
333    def avail_delay_step(self) -> int:
334        """
335        Get the step size for the delay for the trigger input lines in number of sample clocks (see register 'SPC_TRIG_AVAILDELAY_STEP' in chapter `Trigger` in the manual)
336        
337        Returns
338        -------
339        int
340            The step size for the delay for the trigger input lines
341        """
342
343        return self.card.get_i(SPC_TRIG_AVAILDELAY_STEP)

Get the step size for the delay for the trigger input lines in number of sample clocks (see register 'SPC_TRIG_AVAILDELAY_STEP' in chapter Trigger in the manual)

Returns
  • int: The step size for the delay for the trigger input lines
def trigger_counter(self) -> int:
346    def trigger_counter(self) -> int:
347        """
348        Get the number of trigger events since acquisition start (see register 'SPC_TRIGGERCOUNTER' in chapter `Trigger` in the manual)
349        
350        Returns
351        -------
352        int
353            The trigger counter
354        """
355
356        return self.card.get_i(SPC_TRIGGERCOUNTER)

Get the number of trigger events since acquisition start (see register 'SPC_TRIGGERCOUNTER' in chapter Trigger in the manual)

Returns
  • int: The trigger counter
def ext0_mode(self, mode: int = None) -> int:
359    def ext0_mode(self, mode : int = None) -> int:
360        """
361        Set the mode for the main external window trigger (ext0/Trg0) (see register 'SPC_TRIG_EXT0_MODE' in chapter `Trigger` in the manual)
362        
363        Parameters
364        ----------
365        mode : int
366            The mode for the main external window trigger (ext0/Trg0)
367        
368        Returns
369        -------
370        int
371            The mode for the main external window trigger (ext0/Trg0)
372        """
373
374        if mode is not None:
375            self.card.set_i(SPC_TRIG_EXT0_MODE, mode)
376        return self.card.get_i(SPC_TRIG_EXT0_MODE)

Set the mode for the main external window trigger (ext0/Trg0) (see register 'SPC_TRIG_EXT0_MODE' in chapter Trigger in the manual)

Parameters
  • mode (int): The mode for the main external window trigger (ext0/Trg0)
Returns
  • int: The mode for the main external window trigger (ext0/Trg0)
def termination(self, termination: int = None) -> int:
379    def termination(self, termination : int = None) -> int:
380        """
381        Set the trigger termination (see register 'SPC_TRIG_TERM' in chapter `Trigger` in the manual)
382        
383        Parameters
384        ----------
385        termination : int
386            The trigger termination: a „1“ sets the 50 Ohm termination for external trigger signals. A „0“ sets the high impedance termination
387        
388        Returns
389        -------
390        int
391            The trigger termination: a „1“ sets the 50 Ohm termination for external trigger signals. A „0“ sets the high impedance termination
392        """
393
394        if termination is not None:
395            self.card.set_i(SPC_TRIG_TERM, termination)
396        return self.card.get_i(SPC_TRIG_TERM)

Set the trigger termination (see register 'SPC_TRIG_TERM' in chapter Trigger in the manual)

Parameters
  • termination (int): The trigger termination: a „1“ sets the 50 Ohm termination for external trigger signals. A „0“ sets the high impedance termination
Returns
  • int: The trigger termination: a „1“ sets the 50 Ohm termination for external trigger signals. A „0“ sets the high impedance termination
def ext0_coupling(self, coupling: int = None) -> int:
399    def ext0_coupling(self, coupling : int = None) -> int:
400        """
401        Set the trigger input coupling (see hardware manual register name 'SPC_TRIG_EXT0_ACDC')
402        
403        Parameters
404        ----------
405        coupling : int
406            The trigger input coupling: COUPLING_DC enables DC coupling, COUPLING_AC enables AC coupling for the external trigger 
407            input (AC coupling is the default).
408
409        Returns
410        -------
411        int
412            The trigger input coupling: COUPLING_DC enables DC coupling, COUPLING_AC enables AC coupling for the external trigger 
413            input (AC coupling is the default).
414        """
415
416        if coupling is not None:
417            self.card.set_i(SPC_TRIG_EXT0_ACDC, coupling)
418        return self.card.get_i(SPC_TRIG_EXT0_ACDC)

Set the trigger input coupling (see hardware manual register name 'SPC_TRIG_EXT0_ACDC')

Parameters
  • coupling (int): The trigger input coupling: COUPLING_DC enables DC coupling, COUPLING_AC enables AC coupling for the external trigger input (AC coupling is the default).
Returns
  • int: The trigger input coupling: COUPLING_DC enables DC coupling, COUPLING_AC enables AC coupling for the external trigger input (AC coupling is the default).
def ext1_mode(self, mode: int = None) -> int:
421    def ext1_mode(self, mode : int = None) -> int:
422        """
423        Set the mode for the ext1 trigger (see register 'SPC_TRIG_EXT1_MODE' in chapter `Trigger` in the manual)
424        
425        Parameters
426        ----------
427        mode : int
428            The mode for the ext1 trigger
429        
430        Returns
431        -------
432        int
433            The mode for the ext1 trigger
434        """
435
436        if mode is not None:
437            self.card.set_i(SPC_TRIG_EXT1_MODE, mode)
438        return self.card.get_i(SPC_TRIG_EXT1_MODE)

Set the mode for the ext1 trigger (see register 'SPC_TRIG_EXT1_MODE' in chapter Trigger in the manual)

Parameters
  • mode (int): The mode for the ext1 trigger
Returns
  • int: The mode for the ext1 trigger
def ext0_level0(self, level=None, return_unit=None) -> int:
441    def ext0_level0(self, level = None, return_unit = None) -> int:
442        """
443        Set the trigger level 0 for the ext0 trigger (see register 'SPC_TRIG_EXT0_LEVEL0' in chapter `Trigger` in the manual)
444        
445        Parameters
446        ----------
447        level : int
448            The trigger level 0 for the ext0 trigger in mV
449        return_unit : pint.Unit
450            The unit to return the value in
451        
452        Returns
453        -------
454        int | pint.Quantity
455            The trigger level 0 for the ext0 trigger in mV or in the specified unit
456        """
457
458        if level is not None:
459            level = UnitConversion.convert(level, units.mV, int)
460            self.card.set_i(SPC_TRIG_EXT0_LEVEL0, level)
461        return_value = self.card.get_i(SPC_TRIG_EXT0_LEVEL0)
462        return UnitConversion.to_unit(return_value * units.mV, return_unit)

Set the trigger level 0 for the ext0 trigger (see register 'SPC_TRIG_EXT0_LEVEL0' in chapter Trigger in the manual)

Parameters
  • level (int): The trigger level 0 for the ext0 trigger in mV
  • return_unit (pint.Unit): The unit to return the value in
Returns
  • int | pint.Quantity: The trigger level 0 for the ext0 trigger in mV or in the specified unit
def ext0_level1(self, level=None, return_unit=None) -> int:
464    def ext0_level1(self, level = None, return_unit = None) -> int:
465        """
466        Set the trigger level 1 for the ext0 trigger (see register 'SPC_TRIG_EXT0_LEVEL1' in chapter `Trigger` in the manual)
467        
468        Parameters
469        ----------
470        level : int
471            The trigger level for the ext0 trigger in mV
472        return_unit : pint.Unit
473            The unit to return the value in
474        
475        Returns
476        -------
477        int | pint.Quantity
478            The trigger level for the ext0 trigger in mV or in the specified unit
479        """
480
481        if level is not None:
482            level = UnitConversion.convert(level, units.mV, int)
483            self.card.set_i(SPC_TRIG_EXT0_LEVEL1, level)
484        return_value = self.card.get_i(SPC_TRIG_EXT0_LEVEL1)
485        return UnitConversion.to_unit(return_value * units.mV, return_unit)

Set the trigger level 1 for the ext0 trigger (see register 'SPC_TRIG_EXT0_LEVEL1' in chapter Trigger in the manual)

Parameters
  • level (int): The trigger level for the ext0 trigger in mV
  • return_unit (pint.Unit): The unit to return the value in
Returns
  • int | pint.Quantity: The trigger level for the ext0 trigger in mV or in the specified unit
def ext1_level0(self, level=None, return_unit=None) -> int:
487    def ext1_level0(self, level = None, return_unit = None) -> int:
488        """
489        Set the trigger level 0 for the ext1 trigger (see register 'SPC_TRIG_EXT1_LEVEL0' in chapter `Trigger` in the manual)
490        
491        Parameters
492        ----------
493        level : int
494            The trigger level 0 for the ext1 trigger in mV
495        return_unit : pint.Unit
496            The unit to return the value in
497        
498        Returns
499        -------
500        int | pint.Quantity
501            The trigger level 0 for the ext1 trigger in mV or in the specified unit
502        """
503
504        if level is not None:
505            level = UnitConversion.convert(level, units.mV, int)
506            self.card.set_i(SPC_TRIG_EXT1_LEVEL0, level)
507        return_value = self.card.get_i(SPC_TRIG_EXT1_LEVEL0)
508        return UnitConversion.to_unit(return_value * units.mV, return_unit)

Set the trigger level 0 for the ext1 trigger (see register 'SPC_TRIG_EXT1_LEVEL0' in chapter Trigger in the manual)

Parameters
  • level (int): The trigger level 0 for the ext1 trigger in mV
  • return_unit (pint.Unit): The unit to return the value in
Returns
  • int | pint.Quantity: The trigger level 0 for the ext1 trigger in mV or in the specified unit
class MultiPurposeIOs(spcm.CardFunctionality):
 94class MultiPurposeIOs(CardFunctionality):
 95    """a higher-level abstraction of the CardFunctionality class to implement the Card's Multi purpose I/O functionality"""
 96
 97    xio_lines : list[MultiPurposeIO] = []
 98    num_xio_lines : int = None
 99
100    def __init__(self, card : Card, *args, **kwargs) -> None:
101        """
102        Constructor for the MultiPurposeIO class
103    
104        Parameters
105        ----------
106        card : Card
107            The card object to communicate with the card
108        """
109
110        super().__init__(card, *args, **kwargs)
111        
112        self.xio_lines = []
113        self.num_xio_lines = self.get_num_xio_lines()
114        self.load()
115    
116    def __str__(self) -> str:
117        """
118        String representation of the MultiPurposeIO class
119    
120        Returns
121        -------
122        str
123            String representation of the MultiPurposeIO class
124        """
125        
126        return f"MultiPurposeIOs(card={self.card})"
127    
128    __repr__ = __str__
129    def __iter__(self) -> "MultiPurposeIOs":
130        """Define this class as an iterator"""
131        return self
132    
133    def __getitem__(self, index : int) -> MultiPurposeIO:
134        """
135        Get the xio line at the given index
136
137        Parameters
138        ----------
139        index : int
140            The index of the xio line to be returned
141        
142        Returns
143        -------
144        MultiPurposeIO
145            The xio line at the given index
146        """
147
148        return self.xio_lines[index]
149    
150    _xio_iterator_index = -1
151    def __next__(self) -> MultiPurposeIO:
152        """
153        This method is called when the next element is requested from the iterator
154
155        Returns
156        -------
157        MultiPurposeIO
158            The next xio line in the iterator
159        
160        Raises
161        ------
162        StopIteration
163        """
164        self._xio_iterator_index += 1
165        if self._xio_iterator_index >= len(self.xio_lines):
166            self._xio_iterator_index = -1
167            raise StopIteration
168        return self.xio_lines[self._xio_iterator_index]
169    
170    def __len__(self) -> int:
171        """Returns the number of available xio lines of the card"""
172        return len(self.xio_lines)
173    
174
175    def get_num_xio_lines(self) -> int:
176        """
177        Returns the number of digital input/output lines of the card (see register 'SPCM_NUM_XIO_LINES' in chapter `Multi Purpose I/O Lines` in the manual)
178    
179        Returns
180        -------
181        int
182            The number of digital input/output lines of the card
183
184        """
185
186        return self.card.get_i(SPC_NUM_XIO_LINES)
187
188    def load(self) -> None:
189        """
190        Loads the digital input/output lines of the card
191        """
192
193        self.xio_lines = [MultiPurposeIO(self.card, x_index) for x_index in range(self.num_xio_lines)]
194    
195    def asyncio(self, output : int = None) -> int:
196        """
197        Sets the async input/output of the card (see register 'SPCM_XX_ASYNCIO' in chapter `Multi Purpose I/O Lines` in the manual)
198    
199        Parameters
200        ----------
201        output : int
202            The async input/output of the card
203
204        Returns
205        -------
206        int
207            The async input/output of the card
208        """
209
210        if output is not None:
211            self.card.set_i(SPCM_XX_ASYNCIO, output)
212        return self.card.get_i(SPCM_XX_ASYNCIO)

a higher-level abstraction of the CardFunctionality class to implement the Card's Multi purpose I/O functionality

MultiPurposeIOs(card: Card, *args, **kwargs)
100    def __init__(self, card : Card, *args, **kwargs) -> None:
101        """
102        Constructor for the MultiPurposeIO class
103    
104        Parameters
105        ----------
106        card : Card
107            The card object to communicate with the card
108        """
109
110        super().__init__(card, *args, **kwargs)
111        
112        self.xio_lines = []
113        self.num_xio_lines = self.get_num_xio_lines()
114        self.load()

Constructor for the MultiPurposeIO class

Parameters
  • card (Card): The card object to communicate with the card
xio_lines: list[MultiPurposeIO] = []
num_xio_lines: int = None
def get_num_xio_lines(self) -> int:
175    def get_num_xio_lines(self) -> int:
176        """
177        Returns the number of digital input/output lines of the card (see register 'SPCM_NUM_XIO_LINES' in chapter `Multi Purpose I/O Lines` in the manual)
178    
179        Returns
180        -------
181        int
182            The number of digital input/output lines of the card
183
184        """
185
186        return self.card.get_i(SPC_NUM_XIO_LINES)

Returns the number of digital input/output lines of the card (see register 'SPCM_NUM_XIO_LINES' in chapter Multi Purpose I/O Lines in the manual)

Returns
  • int: The number of digital input/output lines of the card
def load(self) -> None:
188    def load(self) -> None:
189        """
190        Loads the digital input/output lines of the card
191        """
192
193        self.xio_lines = [MultiPurposeIO(self.card, x_index) for x_index in range(self.num_xio_lines)]

Loads the digital input/output lines of the card

def asyncio(self, output: int = None) -> int:
195    def asyncio(self, output : int = None) -> int:
196        """
197        Sets the async input/output of the card (see register 'SPCM_XX_ASYNCIO' in chapter `Multi Purpose I/O Lines` in the manual)
198    
199        Parameters
200        ----------
201        output : int
202            The async input/output of the card
203
204        Returns
205        -------
206        int
207            The async input/output of the card
208        """
209
210        if output is not None:
211            self.card.set_i(SPCM_XX_ASYNCIO, output)
212        return self.card.get_i(SPCM_XX_ASYNCIO)

Sets the async input/output of the card (see register 'SPCM_XX_ASYNCIO' in chapter Multi Purpose I/O Lines in the manual)

Parameters
  • output (int): The async input/output of the card
Returns
  • int: The async input/output of the card
class MultiPurposeIO:
 9class MultiPurposeIO:
10    """a higher-level abstraction of the CardFunctionality class to implement the Card's Multi purpose I/O functionality"""
11
12    card : Card = None
13    x_index : int = None
14
15    def __init__(self, card : Card, x_index : int = None) -> None:
16        """
17        Constructor for the MultiPurposeIO class
18    
19        Parameters
20        ----------
21        card : Card
22            The card object to communicate with the card
23        x_index : int
24            The index of the digital input/output to be enabled.
25        """
26
27        self.card = card
28        self.x_index = x_index
29    
30    def __str__(self) -> str:
31        """
32        String representation of the MultiPurposeIO class
33    
34        Returns
35        -------
36        str
37            String representation of the MultiPurposeIO class
38        """
39        
40        return f"MultiPurposeIO(card={self.card}, x_index={self.x_index})"
41    
42    __repr__ = __str__
43
44    def avail_modes(self) -> int:
45        """
46        Returns the available modes of the digital input/output of the card (see register 'SPCM_X0_AVAILMODES' in chapter `Multi Purpose I/O Lines` in the manual)
47
48        Returns
49        -------
50        int
51            The available modes of the digital input/output
52        """
53
54        return self.card.get_i(SPCM_X0_AVAILMODES + self.x_index)
55
56    def x_mode(self, mode : int = None) -> int:
57        """
58        Sets the mode of the digital input/output of the card (see register 'SPCM_X0_MODE' in chapter `Multi Purpose I/O Lines` in the manual)
59    
60        Parameters
61        ----------
62        mode : int
63            The mode of the digital input/output
64        
65        Returns
66        -------
67        int
68            The mode of the digital input/output
69        """
70
71        if mode is not None:
72            self.card.set_i(SPCM_X0_MODE + self.x_index, mode)
73        return self.card.get_i(SPCM_X0_MODE + self.x_index)
74    
75    def dig_mode(self, mode : int = None) -> int:
76        """
77        Sets the digital input/output mode of the xio line (see register 'SPCM_DIGMODE0' in chapter `Multi Purpose I/O Lines` in the manual)
78    
79        Parameters
80        ----------
81        mode : int
82            The digital input/output mode of the xio line
83
84        Returns
85        -------
86        int
87            The digital input/output mode of the xio line
88        """
89
90        if mode is not None:
91            self.card.set_i(SPC_DIGMODE0 + self.x_index, mode)
92        return self.card.get_i(SPC_DIGMODE0 + self.x_index)

a higher-level abstraction of the CardFunctionality class to implement the Card's Multi purpose I/O functionality

MultiPurposeIO(card: Card, x_index: int = None)
15    def __init__(self, card : Card, x_index : int = None) -> None:
16        """
17        Constructor for the MultiPurposeIO class
18    
19        Parameters
20        ----------
21        card : Card
22            The card object to communicate with the card
23        x_index : int
24            The index of the digital input/output to be enabled.
25        """
26
27        self.card = card
28        self.x_index = x_index

Constructor for the MultiPurposeIO class

Parameters
  • card (Card): The card object to communicate with the card
  • x_index (int): The index of the digital input/output to be enabled.
card: Card = None
x_index: int = None
def avail_modes(self) -> int:
44    def avail_modes(self) -> int:
45        """
46        Returns the available modes of the digital input/output of the card (see register 'SPCM_X0_AVAILMODES' in chapter `Multi Purpose I/O Lines` in the manual)
47
48        Returns
49        -------
50        int
51            The available modes of the digital input/output
52        """
53
54        return self.card.get_i(SPCM_X0_AVAILMODES + self.x_index)

Returns the available modes of the digital input/output of the card (see register 'SPCM_X0_AVAILMODES' in chapter Multi Purpose I/O Lines in the manual)

Returns
  • int: The available modes of the digital input/output
def x_mode(self, mode: int = None) -> int:
56    def x_mode(self, mode : int = None) -> int:
57        """
58        Sets the mode of the digital input/output of the card (see register 'SPCM_X0_MODE' in chapter `Multi Purpose I/O Lines` in the manual)
59    
60        Parameters
61        ----------
62        mode : int
63            The mode of the digital input/output
64        
65        Returns
66        -------
67        int
68            The mode of the digital input/output
69        """
70
71        if mode is not None:
72            self.card.set_i(SPCM_X0_MODE + self.x_index, mode)
73        return self.card.get_i(SPCM_X0_MODE + self.x_index)

Sets the mode of the digital input/output of the card (see register 'SPCM_X0_MODE' in chapter Multi Purpose I/O Lines in the manual)

Parameters
  • mode (int): The mode of the digital input/output
Returns
  • int: The mode of the digital input/output
def dig_mode(self, mode: int = None) -> int:
75    def dig_mode(self, mode : int = None) -> int:
76        """
77        Sets the digital input/output mode of the xio line (see register 'SPCM_DIGMODE0' in chapter `Multi Purpose I/O Lines` in the manual)
78    
79        Parameters
80        ----------
81        mode : int
82            The digital input/output mode of the xio line
83
84        Returns
85        -------
86        int
87            The digital input/output mode of the xio line
88        """
89
90        if mode is not None:
91            self.card.set_i(SPC_DIGMODE0 + self.x_index, mode)
92        return self.card.get_i(SPC_DIGMODE0 + self.x_index)

Sets the digital input/output mode of the xio line (see register 'SPCM_DIGMODE0' in chapter Multi Purpose I/O Lines in the manual)

Parameters
  • mode (int): The digital input/output mode of the xio line
Returns
  • int: The digital input/output mode of the xio line
class DataTransfer(spcm.CardFunctionality):
  21class DataTransfer(CardFunctionality):
  22    """
  23    A high-level class to control Data Transfer to and from Spectrum Instrumentation cards.
  24
  25    This class is an iterator class that implements the functions `__iter__` and `__next__`.
  26    This allows the user to supply the class to a for loop and iterate over the data that 
  27    is transferred from or to the card. Each iteration will return a numpy array with a data
  28    block of size `notify_samples`. In case of a digitizer you can read the data from that 
  29    block and process it. In case of a generator you can write data to the block and it's 
  30    then transferred.
  31
  32    For more information about what setups are available, please have a look at the user manual
  33    for your specific card.
  34
  35    Parameters
  36    ----------
  37    `buffer` : NDArray[np.int_]
  38        numpy object that can be used to write data into the spcm buffer
  39    `buffer_size`: int
  40        defines the size of the current buffer shared between the PC and the card
  41    `buffer_type`: int
  42        defines the type of data in the buffer that is used for the transfer
  43    `num_channels`: int
  44        defines the number of channels that are used for the transfer
  45    `bytes_per_sample`: int
  46        defines the number of bytes per sample
  47    `bits_per_sample`: int
  48        defines the number of bits per sample
  49    `direction` : Direction = Direction.Acquisition
  50        Direction of the data transfer.
  51
  52    """
  53    # public
  54    buffer_size : int = 0
  55    notify_size : int = 0
  56
  57    direction : Direction = Direction.Acquisition
  58
  59    buffer_type : int
  60    num_channels : int = 0
  61    bytes_per_sample : int = 0
  62    bits_per_sample : int = 0
  63
  64    current_user_pos : int = 0
  65
  66    _polling = False
  67    _polling_timer = 0
  68
  69    # private
  70    _buffer_samples : int = 0
  71    _notify_samples : int = 0
  72
  73    # private
  74    _memory_size : int = 0
  75    _c_buffer = None # Internal numpy ctypes buffer object
  76    _buffer_alignment : int = 4096
  77    _np_buffer : npt.NDArray[np.int_] # Internal object on which the getter setter logic is working
  78    _bit_buffer : npt.NDArray[np.int_]
  79    _8bit_mode : bool = False
  80    _12bit_mode : bool = False
  81    _pre_trigger : int = 0
  82
  83    def __init__(self, card, *args, **kwargs) -> None:
  84        """
  85        Initialize the DataTransfer object with a card object and additional arguments
  86
  87        Parameters
  88        ----------
  89        card : Card
  90            the card object that is used for the data transfer
  91        *args : list
  92            list of additional arguments
  93        **kwargs : dict
  94            dictionary of additional keyword arguments
  95        """
  96        
  97        self.buffer_size = 0
  98        self.notify_size = 0
  99        self.num_channels = 0
 100        self.bytes_per_sample = 0
 101        self.bits_per_sample = 0
 102
 103        self.current_user_pos = 0
 104
 105        self._buffer_samples = 0
 106        self._notify_samples = 0
 107        self._memory_size = 0
 108        self._c_buffer = None
 109        self._buffer_alignment = 4096
 110        self._np_buffer = None
 111        self._bit_buffer = None
 112        self._8bit_mode = False
 113        self._12bit_mode = False
 114        self._pre_trigger = 0
 115        
 116        super().__init__(card, *args, **kwargs)
 117        self.buffer_type = SPCM_BUF_DATA
 118        self._bytes_per_sample()
 119        self._bits_per_sample()
 120        self.num_channels = self.card.active_channels()
 121
 122        # Find out the direction of transfer
 123        if self.function_type == SPCM_TYPE_AI or self.function_type == SPCM_TYPE_DI:
 124            self.direction = Direction.Acquisition
 125        elif self.function_type == SPCM_TYPE_AO or self.function_type == SPCM_TYPE_DO:
 126            self.direction = Direction.Generation
 127        else:
 128            self.direction = Direction.Undefined
 129    
 130
 131    @property
 132    def buffer(self) -> npt.NDArray[np.int_]:
 133        """
 134        The numpy buffer object that interfaces the Card and can be written and read from
 135        
 136        Returns
 137        -------
 138        numpy array
 139            the numpy buffer object with the following array index definition: 
 140            `[channel, sample]`
 141            or in case of multiple recording / replay:
 142            `[segment, sample, channel]`
 143        """
 144        return self._np_buffer
 145    
 146    @buffer.setter
 147    def buffer(self, value) -> None:
 148        self._np_buffer = value
 149    
 150    @buffer.deleter
 151    def buffer(self) -> None:
 152        del self._np_buffer
 153    
 154    @property
 155    def bit_buffer(self) -> npt.NDArray[np.int_]:
 156        """
 157        The bit buffer object that interfaces the Card and can be written and read from
 158        
 159        Returns
 160        -------
 161        numpy array
 162            with the buffer object where all the individual bits are now unpacked
 163        """
 164
 165        # self._bit_buffer = self.unpackbits(self._np_buffer) # not a good solution
 166        return self._bit_buffer
 167    
 168    @bit_buffer.setter
 169    def bit_buffer(self, value) -> None:
 170        self._bit_buffer = value
 171    
 172    @bit_buffer.deleter
 173    def bit_buffer(self) -> None:
 174        del self._bit_buffer
 175
 176    @property
 177    def buffer_samples(self) -> int:
 178        """
 179        The number of samples in the buffer
 180        
 181        Returns
 182        -------
 183        int
 184            the number of samples in the buffer
 185        """
 186        return self._buffer_samples
 187    
 188    @buffer_samples.setter
 189    def buffer_samples(self, value) -> None:
 190        if value is not None:
 191            self._buffer_samples = value
 192            self.buffer_size = self.samples_to_bytes(self._buffer_samples)
 193    
 194    @buffer_samples.deleter
 195    def buffer_samples(self) -> None:
 196        del self._buffer_samples
 197
 198    def _bits_per_sample(self) -> int:
 199        """
 200        Get the number of bits per sample
 201
 202        Returns
 203        -------
 204        int
 205            number of bits per sample
 206        """
 207        if self._8bit_mode:
 208            self.bits_per_sample = 8
 209        elif self._12bit_mode:
 210            self.bits_per_sample = 12        
 211        else:
 212            self.bits_per_sample = self.card.bits_per_sample()
 213        return self.bits_per_sample
 214    
 215    def _bytes_per_sample(self) -> int:
 216        """
 217        Get the number of bytes per sample
 218
 219        Returns
 220        -------
 221        int
 222            number of bytes per sample
 223        """
 224        if self._8bit_mode:
 225            self.bytes_per_sample = 1
 226        elif self._12bit_mode:
 227            self.bytes_per_sample = 1.5
 228        else:
 229            self.bytes_per_sample = self.card.bytes_per_sample()
 230        return self.bytes_per_sample
 231    
 232    def bytes_to_samples(self, num_bytes : int) -> int:
 233        """
 234        Convert bytes to samples
 235        
 236        Parameters
 237        ----------
 238        bytes : int
 239            the number of bytes
 240        
 241        Returns
 242        -------
 243        int
 244            the number of samples
 245        """
 246
 247        if self.bits_per_sample > 1:
 248            num_samples = num_bytes // self.bytes_per_sample // self.num_channels
 249        else:
 250            num_samples = num_bytes // self.num_channels * 8
 251        return num_samples
 252    
 253    def samples_to_bytes(self, num_samples : int) -> int:
 254        """
 255        Convert samples to bytes
 256        
 257        Parameters
 258        ----------
 259        num_samples : int
 260            the number of samples
 261        
 262        Returns
 263        -------
 264        int
 265            the number of bytes
 266        """
 267
 268        if self.bits_per_sample > 1:
 269            num_bytes = num_samples * self.bytes_per_sample * self.num_channels
 270        else:
 271            num_bytes = num_samples * self.num_channels // 8
 272        return num_bytes
 273    
 274    def notify_samples(self, notify_samples : int = None) -> int:
 275        """
 276        Set the number of samples to notify the user about
 277        
 278        Parameters
 279        ----------
 280        notify_samples : int | pint.Quantity
 281            the number of samples to notify the user about
 282        """
 283
 284        if notify_samples is not None:
 285            notify_samples = UnitConversion.convert(notify_samples, units.Sa, int)
 286            self._notify_samples = notify_samples
 287            self.notify_size = self.samples_to_bytes(self._notify_samples)
 288        return self._notify_samples
 289
 290    def _sample_rate(self) -> pint.Quantity:
 291        """
 292        Get the sample rate of the card
 293
 294        Returns
 295        -------
 296        pint.Quantity
 297            the sample rate of the card in Hz as a pint quantity
 298        """
 299        return self.card.get_i(SPC_SAMPLERATE) * units.Hz
 300
 301    def memory_size(self, memory_samples : int = None) -> int:
 302        """
 303        Sets the memory size in samples per channel. The memory size setting must be set before transferring 
 304        data to the card. (see register `SPC_MEMSIZE` in the manual)
 305        
 306        Parameters
 307        ----------
 308        memory_samples : int | pint.Quantity
 309            the size of the memory in samples
 310        
 311        Returns
 312        -------
 313        int
 314            the size of the memory in samples
 315        """
 316
 317        if memory_samples is not None:
 318            memory_samples = UnitConversion.convert(memory_samples, units.Sa, int)
 319            self.card.set_i(SPC_MEMSIZE, memory_samples)
 320        self._memory_size = self.card.get_i(SPC_MEMSIZE)
 321        return self._memory_size
 322    
 323    def output_buffer_size(self, buffer_samples : int = None) -> int:
 324        """
 325        Set the size of the output buffer (see register `SPC_DATA_OUTBUFSIZE` in the manual)
 326        
 327        Parameters
 328        ----------
 329        buffer_samples : int | pint.Quantity
 330            the size of the output buffer in Bytes
 331        
 332        Returns
 333        -------
 334        int
 335            the size of the output buffer in Samples
 336        """
 337
 338        if buffer_samples is not None:
 339            buffer_samples = UnitConversion.convert(buffer_samples, units.B, int)
 340            buffer_size = self.samples_to_bytes(buffer_samples)
 341            self.card.set_i(SPC_DATA_OUTBUFSIZE, buffer_size)
 342        return self.bytes_to_samples(self.card.get_i(SPC_DATA_OUTBUFSIZE))
 343    
 344    def loops(self, loops : int = None) -> int:
 345        return self.card.loops(loops)
 346    
 347    def pre_trigger(self, num_samples : int = None) -> int:
 348        """
 349        Set the number of pre trigger samples (see register `SPC_PRETRIGGER` in the manual)
 350        
 351        Parameters
 352        ----------
 353        num_samples : int | pint.Quantity
 354            the number of pre trigger samples
 355        
 356        Returns
 357        -------
 358        int
 359            the number of pre trigger samples
 360        """
 361
 362        if num_samples is not None:
 363            num_samples = UnitConversion.convert(num_samples, units.Sa, int)
 364            self.card.set_i(SPC_PRETRIGGER, num_samples)
 365        self._pre_trigger = self.card.get_i(SPC_PRETRIGGER)
 366        return self._pre_trigger
 367    
 368    def post_trigger(self, num_samples : int = None, set_pre_trigger : bool = True) -> int:
 369        """
 370        Set the number of post trigger samples (see register `SPC_POSTTRIGGER` in the manual)
 371        
 372        Parameters
 373        ----------
 374        num_samples : int | pint.Quantity
 375            the number of post trigger samples
 376        set_pre_trigger : bool = True
 377            if True, the pre trigger samples are set to the memory size minus the post trigger samples.
 378        
 379        Returns
 380        -------
 381        int
 382            the number of post trigger samples
 383        """
 384
 385        if set_pre_trigger and self._memory_size < num_samples:
 386            raise ValueError(f"The number of post trigger samples needs to be smaller than the total number of samples: {self._memory_size} < {num_samples}")
 387        if num_samples is not None:
 388            num_samples = UnitConversion.convert(num_samples, units.Sa, int)
 389            self.card.set_i(SPC_POSTTRIGGER, num_samples)
 390        post_trigger = self.card.get_i(SPC_POSTTRIGGER)
 391        if set_pre_trigger:
 392            self._pre_trigger = self._memory_size - post_trigger
 393        return post_trigger
 394    
 395    def allocate_buffer(self, num_samples : int, no_reshape = False) -> None:
 396        """
 397        Memory allocation for the buffer that is used for communicating with the card
 398
 399        Parameters
 400        ----------
 401        num_samples : int | pint.Quantity = None
 402            use the number of samples an get the number of active channels and bytes per samples directly from the card
 403        no_reshape : bool = False
 404            if True, the buffer is not reshaped to the number of channels. This is useful for digital cards where the data is packed in a single array. 
 405            As well as for 12bit cards where three data points are packed in four bytes or two 16-bit samples.
 406        """
 407
 408        self.buffer_samples = UnitConversion.convert(num_samples, units.Sa, int)
 409        no_reshape |= self._12bit_mode | self.bits_per_sample == 1
 410        self.buffer = self._allocate_buffer(self.buffer_samples, no_reshape, self.num_channels)
 411        if self.bits_per_sample == 1:
 412            self.unpackbits() # allocate the bit buffer for digital cards
 413
 414    
 415    def _allocate_buffer(self, num_samples : int, no_reshape : bool = False, num_channels : int = 1) -> npt.NDArray:
 416        """
 417        Memory allocation for the buffer that is used for communicating with the card
 418
 419        Parameters
 420        ----------
 421        num_samples : int | pint.Quantity = None
 422            use the number of samples an get the number of active channels and bytes per samples directly from the card
 423        no_reshape : bool = False
 424            if True, the buffer is not reshaped to the number of channels. This is useful for digital cards where the data is packed in a single array.
 425        
 426        Returns
 427        -------
 428        numpy array
 429            the allocated buffer
 430        """
 431        
 432        buffer_size = self.samples_to_bytes(num_samples)
 433        sample_type = self.numpy_type()
 434
 435        dwMask = self._buffer_alignment - 1
 436
 437        item_size = sample_type(0).itemsize
 438        # allocate a buffer (numpy array) for DMA transfer: a little bigger one to have room for address alignment
 439        databuffer_unaligned = np.empty(((self._buffer_alignment + buffer_size) // item_size, ), dtype = sample_type)   # byte count to sample (// = integer division)
 440        # two numpy-arrays may share the same memory: skip the begin up to the alignment boundary (ArrayVariable[SKIP_VALUE:])
 441        # Address of data-memory from numpy-array: ArrayVariable.__array_interface__['data'][0]
 442        start_pos_samples = ((self._buffer_alignment - (databuffer_unaligned.__array_interface__['data'][0] & dwMask)) // item_size)
 443        buffer = databuffer_unaligned[start_pos_samples:start_pos_samples + (buffer_size // item_size)]   # byte address to sample size
 444        if not no_reshape:
 445            buffer = buffer.reshape((num_channels, num_samples), order='F')  # index definition: [channel, sample] !
 446        return buffer
 447    
 448    def start_buffer_transfer(self, *args, buffer_type=SPCM_BUF_DATA, direction=None, notify_samples=None, transfer_offset=None, transfer_length=None, exception_num_samples=False) -> None:
 449        """
 450        Start the transfer of the data to or from the card  (see the API function `spcm_dwDefTransfer_i64` in the manual)
 451        
 452        Parameters
 453        ----------
 454        *args : list
 455            list of additonal arguments that are added as flags to the start dma command
 456        buffer_type : int
 457            the type of buffer that is used for the transfer
 458        direction : int
 459            the direction of the transfer
 460        notify_samples : int
 461            the number of samples to notify the user about
 462        transfer_offset : int
 463            the offset of the transfer
 464        transfer_length : int
 465            the length of the transfer
 466        exception_num_samples : bool
 467            if True, an exception is raised if the number of samples is not a multiple of the notify samples. The automatic buffer handling only works with the number of samples being a multiple of the notify samples.
 468
 469        Raises
 470        ------
 471        SpcmException
 472        """
 473
 474        self.notify_samples(UnitConversion.convert(notify_samples, units.Sa, int))
 475        transfer_offset = UnitConversion.convert(transfer_offset, units.Sa, int)
 476        transfer_length = UnitConversion.convert(transfer_length, units.Sa, int)
 477
 478        if transfer_length is not None:
 479            self.buffer_samples = transfer_length
 480
 481        if self.buffer is None: 
 482            raise SpcmException(text="No buffer defined for transfer")
 483        if buffer_type: 
 484            self.buffer_type = buffer_type
 485        if direction is None:
 486            if self.direction == Direction.Acquisition:
 487                direction = SPCM_DIR_CARDTOPC
 488            elif self.direction == Direction.Generation:
 489                direction = SPCM_DIR_PCTOCARD
 490            else:
 491                raise SpcmException(text="Please define a direction for transfer (SPCM_DIR_CARDTOPC or SPCM_DIR_PCTOCARD)")
 492        
 493        if self._notify_samples != 0 and np.remainder(self.buffer_samples, self._notify_samples) and exception_num_samples:
 494            raise SpcmException("The number of samples needs to be a multiple of the notify samples.")
 495
 496        if transfer_offset:
 497            transfer_offset_bytes = self.samples_to_bytes(transfer_offset)
 498        else:
 499            transfer_offset_bytes = 0
 500        
 501        # we define the buffer for transfer
 502        self.card._print("Starting the DMA transfer and waiting until data is in board memory")
 503        self._c_buffer = self.buffer.ctypes.data_as(c_void_p)
 504        self.card._check_error(spcm_dwDefTransfer_i64(self.card._handle, self.buffer_type, direction, self.notify_size, self._c_buffer, transfer_offset_bytes, self.buffer_size))
 505        
 506        # Execute additional commands if available
 507        if args:
 508            cmd = 0
 509            for arg in args:
 510                cmd |= arg
 511            self.card.cmd(cmd)
 512            self.card._print("... data transfer started")
 513
 514    def duration(self, duration : pint.Quantity, pre_trigger_duration : pint.Quantity = None, post_trigger_duration : pint.Quantity = None) -> None:
 515        """
 516        Set the duration of the data transfer
 517        
 518        Parameters
 519        ----------
 520        duration : pint.Quantity
 521            the duration of the data transfer
 522        pre_trigger_duration : pint.Quantity = None
 523            the duration before the trigger event
 524        post_trigger_duration : pint.Quantity = None
 525            the duration after the trigger event
 526        
 527        Returns
 528        -------
 529        pint.Quantity
 530            the duration of the data transfer
 531        """
 532
 533        if pre_trigger_duration is None and post_trigger_duration is None:
 534            raise ValueError("Please define either pre_trigger_duration or post_trigger_duration")
 535
 536        memsize_min = self.card.get_i(SPC_AVAILMEMSIZE_MIN)
 537        memsize_max = self.card.get_i(SPC_AVAILMEMSIZE_MAX)
 538        memsize_stp = self.card.get_i(SPC_AVAILMEMSIZE_STEP)
 539        num_samples = (duration * self._sample_rate()).to_base_units().magnitude
 540        num_samples = np.ceil(num_samples / memsize_stp) * memsize_stp
 541        num_samples = np.clip(num_samples, memsize_min, memsize_max)
 542        num_samples = int(num_samples)
 543        self.memory_size(num_samples)
 544        self.allocate_buffer(num_samples)
 545        if pre_trigger_duration is not None:
 546            pre_min = self.card.get_i(SPC_AVAILPRETRIGGER_MIN)
 547            pre_max = self.card.get_i(SPC_AVAILPRETRIGGER_MAX)
 548            pre_stp = self.card.get_i(SPC_AVAILPRETRIGGER_STEP)
 549            pre_samples = (pre_trigger_duration * self._sample_rate()).to_base_units().magnitude
 550            pre_samples = np.ceil(pre_samples / pre_stp) * pre_stp
 551            pre_samples = np.clip(pre_samples, pre_min, pre_max)
 552            pre_samples = int(post_samples)
 553            self.post_trigger(post_samples)
 554        if post_trigger_duration is not None:
 555            post_min = self.card.get_i(SPC_AVAILPOSTTRIGGER_MIN)
 556            post_max = self.card.get_i(SPC_AVAILPOSTTRIGGER_MAX)
 557            post_stp = self.card.get_i(SPC_AVAILPOSTTRIGGER_STEP)
 558            post_samples = (post_trigger_duration * self._sample_rate()).to_base_units().magnitude
 559            post_samples = np.ceil(post_samples / post_stp) * post_stp
 560            post_samples = np.clip(post_samples, post_min, post_max)
 561            post_samples = int(post_samples)
 562            self.post_trigger(post_samples)
 563        return num_samples, post_samples
 564
 565    def time_data(self, total_num_samples : int = None, return_units = units.s) -> npt.NDArray:
 566        """
 567        Get the time array for the data buffer
 568
 569        Parameters
 570        ----------
 571        total_num_samples : int | pint.Quantity
 572            the total number of samples
 573        return_units : pint.Quantity
 574            the units that the time should be converted to
 575        
 576        Returns
 577        -------
 578        numpy array
 579            the time array
 580        """
 581
 582        if total_num_samples is None:
 583            total_num_samples = self._buffer_samples
 584        total_num_samples = UnitConversion.convert(total_num_samples, units.Sa, int)
 585        pre_trigger = UnitConversion.convert(self._pre_trigger, units.Sa, int)
 586        return self.convert_time((np.arange(total_num_samples) - pre_trigger)).to(return_units)
 587    
 588    def convert_time(self, time, return_units = units.s):
 589        """
 590        Convert a time to the units of the card sample rate
 591        
 592        Parameters
 593        ----------
 594        time : numpy array
 595            the time array with integers that should be converted
 596        return_units : numpy array with pint.Quantity
 597            the units that the time should be converted to
 598        
 599        Returns
 600        -------
 601        pint.Quantity
 602            the converted time
 603        """
 604
 605        sample_rate = self._sample_rate()
 606        return (time / sample_rate).to(return_units)
 607
 608    def unpack_12bit_buffer(self, data : npt.NDArray[np.int_] = None) -> npt.NDArray[np.int_]:
 609        """
 610        Unpacks the 12-bit packed data to 16-bit data
 611        
 612        Parameters
 613        ----------
 614        data : numpy array
 615            the packed data
 616
 617        Returns
 618        -------
 619        numpy array
 620            the unpacked 16bit buffer
 621        """
 622
 623        if not self._12bit_mode:
 624            raise SpcmException("The card is not in 12bit packed mode")
 625        
 626        if data is None:
 627            data = self.buffer
 628
 629        fst_int8, mid_int8, lst_int8 = np.reshape(data, (data.shape[0] // 3, 3)).astype(np.int16).T
 630        nibble_h = (mid_int8 >> 0) & 0x0F
 631        nibble_m = (fst_int8 >> 4) & 0x0F
 632        nibble_l = (fst_int8 >> 0) & 0x0F
 633        fst_int12 = ((nibble_h << 12) >> 4) | (nibble_m << 4) | (nibble_l << 0)
 634        nibble_h = (lst_int8 >> 4) & 0x0F
 635        nibble_m = (lst_int8 >> 0) & 0x0F
 636        nibble_l = (mid_int8 >> 4) & 0x0F
 637        snd_int12 = ((nibble_h << 12) >> 4) | (nibble_m << 4) | (nibble_l << 0)
 638        data_int12 = np.concatenate((fst_int12[:, None], snd_int12[:, None]), axis=1).reshape((-1,))
 639        data_int12 = data_int12.reshape((self.num_channels, self._buffer_samples), order='F')
 640        return data_int12
 641    
 642    def unpackbits(self, data : npt.NDArray[np.int_] = None) -> npt.NDArray[np.int_]:
 643        """
 644        Unpack the buffer to bits
 645
 646        Parameters
 647        ----------
 648        data : numpy array | None = None
 649            the packed data
 650
 651        Returns
 652        -------
 653        numpy array
 654            the unpacked buffer
 655        """
 656
 657        if data is None:
 658            data = self.buffer
 659        dshape = list(data.shape)
 660        return_data = data.reshape([-1, 1])
 661        num_bits = return_data.dtype.itemsize * 8
 662        mask = 2**np.arange(num_bits, dtype=return_data.dtype).reshape([1, num_bits])
 663        self.bit_buffer = (return_data & mask).astype(np.bool).astype(np.uint8).reshape(dshape + [num_bits])
 664        return self.bit_buffer
 665    
 666    def packbits(self) -> None:
 667        """
 668        Pack the self.buffer from the self.bit_buffer
 669        """
 670
 671        self.buffer[:] = np.packbits(self._bit_buffer, axis=-1, bitorder='little').view(self.buffer.dtype).reshape(self.buffer.shape)
 672
 673    def tofile(self, filename : str, buffer = None, **kwargs) -> None:
 674        """
 675        Export the buffer to a file. The file format is determined by the file extension
 676        Supported file formats are: 
 677        * .bin: raw binary file
 678        * .csv: comma-separated values file
 679        * .npy: numpy binary file
 680        * .npz: compressed numpy binary file
 681        * .txt: whitespace-delimited text file
 682        * .h5: hdf5 file format
 683
 684        Parameters
 685        ----------
 686        filename : str
 687            the name of the file that the buffer should be exported to
 688        
 689        Raises
 690        ------
 691        ImportError
 692            if the file format is not supported
 693        """
 694
 695        if buffer is None:
 696            buffer = self.buffer
 697        file_path = Path(filename)
 698        if file_path.suffix == '.bin':
 699            buffer.tofile(file_path)
 700        elif file_path.suffix == '.csv':
 701            delimiter = kwargs.get('delimiter', ',')
 702            np.savetxt(file_path, buffer, delimiter=delimiter)
 703        elif file_path.suffix == '.npy':
 704            np.save(file_path, buffer)
 705        elif file_path.suffix == '.npz':
 706            np.savez_compressed(file_path, buffer)
 707        elif file_path.suffix == '.txt':
 708            np.savetxt(file_path, buffer, fmt='%d')
 709        elif file_path.suffix == '.h5' or file_path.suffix == '.hdf5':
 710            import h5py
 711            with h5py.File(file_path, 'w') as f:
 712                f.create_dataset('data', data=buffer)
 713        else:
 714            raise ImportError("File format not supported")
 715        
 716    def fromfile(self, filename : str, in_buffer : bool = True, **kwargs) -> npt.NDArray[np.int_]:
 717        """
 718        Import the buffer from a file. The file format is determined by the file extension
 719        Supported file formats are: 
 720        * .bin: raw binary file
 721        * .csv: comma-separated values file
 722        * .npy: numpy binary file
 723        * .npz: compressed numpy binary file
 724        * .txt: whitespace-delimited text file
 725        * .h5: hdf5 file format
 726
 727        Parameters
 728        ----------
 729        filename : str
 730            the name of the file that the buffer should be imported from
 731        
 732        Raises
 733        ------
 734        ImportError
 735            if the file format is not supported
 736        """
 737
 738        file_path = Path(filename)
 739        if file_path.suffix == '.bin':
 740            dtype = kwargs.get('dtype', self.numpy_type())
 741            shape = kwargs.get('shape', (self.num_channels, self.buffer_size // self.num_channels))
 742            buffer = np.fromfile(file_path, dtype=dtype)
 743            loaded_data = buffer.reshape(shape, order='C')
 744        elif file_path.suffix == '.csv':
 745            delimiter = kwargs.get('delimiter', ',')
 746            loaded_data = np.loadtxt(file_path, delimiter=delimiter)
 747        elif file_path.suffix == '.npy':
 748            loaded_data = np.load(file_path)
 749        elif file_path.suffix == '.npz':
 750            data = np.load(file_path)
 751            loaded_data = data['arr_0']
 752        elif file_path.suffix == '.txt':
 753            loaded_data = np.loadtxt(file_path)
 754        elif file_path.suffix == '.h5' or file_path.suffix == '.hdf5':
 755            import h5py
 756            with h5py.File(file_path, 'r') as f:
 757                loaded_data = f['data'][()]
 758        else:
 759            raise ImportError("File format not supported")
 760        
 761        if in_buffer:
 762            self.buffer[:] = loaded_data
 763        return loaded_data
 764
 765
 766    def avail_card_len(self, available_samples : int = 0) -> None:
 767        """
 768        Set the amount of data that has been read out of the data buffer (see register `SPC_DATA_AVAIL_CARD_LEN` in the manual)
 769
 770        Parameters
 771        ----------
 772        available_samples : int | pint.Quantity
 773            the amount of data that is available for reading
 774        """
 775
 776        available_samples = UnitConversion.convert(available_samples, units.Sa, int)
 777        # print(available_samples, self.bytes_per_sample, self.num_channels)
 778        available_bytes = self.samples_to_bytes(available_samples)
 779        self.card.set_i(SPC_DATA_AVAIL_CARD_LEN, available_bytes)
 780    
 781    def avail_user_pos(self, in_bytes : bool = False) -> int:
 782        """
 783        Get the current position of the pointer in the data buffer (see register `SPC_DATA_AVAIL_USER_POS` in the manual)
 784
 785        Parameters
 786        ----------
 787        in_bytes : bool
 788            if True, the position is returned in bytes
 789
 790        Returns
 791        -------
 792        int
 793            pointer position
 794        """
 795
 796        self.current_user_pos = self.card.get_i(SPC_DATA_AVAIL_USER_POS)
 797        if not in_bytes:
 798            self.current_user_pos = self.bytes_to_samples(self.current_user_pos)
 799        return self.current_user_pos
 800    
 801    def avail_user_len(self, in_bytes : bool = False) -> int:
 802        """
 803        Get the current length of the data in the data buffer (see register `SPC_DATA_AVAIL_USER_LEN` in the manual)
 804
 805        Parameters
 806        ----------
 807        in_bytes : bool
 808            if True, the length is returned in bytes
 809
 810        Returns
 811        -------
 812        int
 813            data length available
 814        """
 815
 816        user_len = self.card.get_i(SPC_DATA_AVAIL_USER_LEN)
 817        if not in_bytes:
 818            user_len = self.bytes_to_samples(user_len)
 819        return user_len
 820    
 821    def fill_size_promille(self, return_unit = None) -> int:
 822        """
 823        Get the fill size of the data buffer (see register `SPC_FILLSIZEPROMILLE` in the manual)
 824
 825        Returns
 826        -------
 827        int
 828            fill size
 829        """
 830
 831        return_value = self.card.get_i(SPC_FILLSIZEPROMILLE)
 832        if return_unit is not None: return_value = UnitConversion.to_unit(return_value * units.promille, return_unit)
 833        return return_value
 834    
 835    def wait_dma(self) -> None:
 836        """
 837        Wait for the DMA transfer to finish (see register `M2CMD_DATA_WAITDMA` in the manual)
 838        """
 839        
 840        self.card.cmd(M2CMD_DATA_WAITDMA)
 841    wait = wait_dma
 842
 843    def numpy_type(self) -> npt.NDArray[np.int_]:
 844        """
 845        Get the type of numpy data from number of bytes
 846
 847        Returns
 848        -------
 849        numpy data type
 850            the type of data that is used by the card
 851        """
 852
 853        if self._8bit_mode:
 854            return np.uint8
 855        if self._12bit_mode:
 856            return np.int8
 857        if self.bits_per_sample == 1:
 858            if self.num_channels <= 8:
 859                return np.uint8
 860            elif self.num_channels <= 16:
 861                return np.uint16
 862            elif self.num_channels <= 32:
 863                return np.uint32
 864            return np.uint64
 865        if self.bits_per_sample <= 8:
 866            return np.int8
 867        elif self.bits_per_sample <= 16:
 868            return np.int16
 869        elif self.bits_per_sample <= 32:
 870            return np.int32
 871        return np.int64
 872
 873    # Data conversion mode
 874    def data_conversion(self, mode : int = None) -> int:
 875        """
 876        Set the data conversion mode (see register `SPC_DATACONVERSION` in the manual)
 877        
 878        Parameters
 879        ----------
 880        mode : int
 881            the data conversion mode
 882        """
 883
 884        if mode is not None:
 885            self.card.set_i(SPC_DATACONVERSION, mode)
 886        mode = self.card.get_i(SPC_DATACONVERSION)
 887        self._8bit_mode = (mode == SPCM_DC_12BIT_TO_8BIT or mode == SPCM_DC_14BIT_TO_8BIT or mode == SPCM_DC_16BIT_TO_8BIT)
 888        self._12bit_mode = (mode == SPCM_DC_12BIT_TO_12BITPACKED)
 889        self._bits_per_sample()
 890        self._bytes_per_sample()
 891        return mode
 892    
 893    def avail_data_conversion(self) -> int:
 894        """
 895        Get the available data conversion modes (see register `SPC_AVAILDATACONVERSION` in the manual)
 896
 897        Returns
 898        -------
 899        int
 900            the available data conversion modes
 901        """
 902        return self.card.get_i(SPC_AVAILDATACONVERSION)
 903    
 904    # Iterator methods
 905
 906    iterator_index = 0
 907    _max_timeout = 64
 908
 909    _to_transfer_samples = 0
 910    _current_samples = 0
 911    
 912    _verbose = False
 913
 914    def verbose(self, verbose : bool = None) -> bool:
 915        """
 916        Set or get the verbose mode for the data transfer
 917
 918        Parameters
 919        ----------
 920        verbose : bool = None
 921            the verbose mode
 922        """
 923
 924        if verbose is not None:
 925            self._verbose = verbose
 926        return self._verbose
 927
 928    def to_transfer_samples(self, samples) -> None:
 929        """
 930        This method sets the number of samples to transfer
 931
 932        Parameters
 933        ----------
 934        samples : int | pint.Quantity
 935            the number of samples to transfer
 936        """
 937
 938        samples = UnitConversion.convert(samples, units.Sa, int)
 939        self._to_transfer_samples = samples
 940    
 941    def __iter__(self):
 942        """
 943        This method is called when the iterator is initialized
 944
 945        Returns
 946        -------
 947        DataIterator
 948            the iterator itself
 949        """
 950
 951        self.iterator_index = 0
 952        return self
 953    
 954    def polling(self, polling : bool = True, timer : float = 0.01) -> None:
 955        """
 956        Set the polling mode for the data transfer otherwise wait_dma() is used
 957
 958        Parameters
 959        ----------
 960        polling : bool
 961            True to enable polling, False to disable polling
 962        timer : float | pint.Quantity
 963            the polling timer in seconds
 964        """
 965
 966        self._polling = polling
 967        self._polling_timer = UnitConversion.convert(timer, units.s, float, rounding=None)
 968    
 969    _auto_avail_card_len = True
 970    def auto_avail_card_len(self, value : bool = None) -> bool:
 971        """
 972        Enable or disable the automatic sending of the number of samples that the card can now use for sample data transfer again
 973
 974        Parameters
 975        ----------
 976        value : bool = None
 977            True to enable, False to disable and None to get the current status
 978
 979        Returns
 980        -------
 981        bool
 982            the current status
 983        """
 984        if value is not None:
 985            self._auto_avail_card_len = value
 986        return self._auto_avail_card_len
 987
 988    def __next__(self) -> npt.ArrayLike:
 989        """
 990        This method is called when the next element is requested from the iterator
 991
 992        Returns
 993        -------
 994        npt.ArrayLike
 995            the next data block
 996        
 997        Raises
 998        ------
 999        StopIteration
1000        """
1001        timeout_counter = 0
1002        # notify the card that data is available or read, but only after the first block
1003        if self.iterator_index != 0 and self._auto_avail_card_len:
1004            self.flush()
1005
1006        while True:
1007            try:
1008                if not self._polling:
1009                    self.wait_dma()
1010                else:
1011                    user_len = self.avail_user_len()
1012                    if user_len >= self._notify_samples:
1013                        break
1014                    time.sleep(self._polling_timer)
1015            except SpcmTimeout:
1016                self.card._print("... Timeout ({})".format(timeout_counter), end='\r')
1017                timeout_counter += 1
1018                if timeout_counter > self._max_timeout:
1019                    self.iterator_index = 0
1020                    raise StopIteration
1021            else:
1022                if not self._polling:
1023                    break
1024        
1025        self.iterator_index += 1
1026
1027        self._current_samples += self._notify_samples
1028        if self._to_transfer_samples != 0 and self._to_transfer_samples < self._current_samples:
1029            self.iterator_index = 0
1030            raise StopIteration
1031
1032        user_pos = self.avail_user_pos()
1033        fill_size = self.fill_size_promille()
1034        
1035        self.card._print("Fill size: {}%  Pos:{:08x} Total:{:.2f} MiS / {:.2f} MiS".format(fill_size/10, user_pos, self._current_samples / MEBI(1), self._to_transfer_samples / MEBI(1)), end='\r', verbose=self._verbose)
1036        
1037        return self.buffer[:, user_pos:user_pos+self._notify_samples]
1038    
1039    def flush(self):
1040        """
1041        This method is used to tell the card that a notify size of data is freed up after reading (acquisition) or written to (generation)
1042        """
1043        self.avail_card_len(self._notify_samples)

A high-level class to control Data Transfer to and from Spectrum Instrumentation cards.

This class is an iterator class that implements the functions __iter__ and __next__. This allows the user to supply the class to a for loop and iterate over the data that is transferred from or to the card. Each iteration will return a numpy array with a data block of size notify_samples. In case of a digitizer you can read the data from that block and process it. In case of a generator you can write data to the block and it's then transferred.

For more information about what setups are available, please have a look at the user manual for your specific card.

Parameters
  • buffer (NDArray[np.int_]): numpy object that can be used to write data into the spcm buffer
  • buffer_size (int): defines the size of the current buffer shared between the PC and the card
  • buffer_type (int): defines the type of data in the buffer that is used for the transfer
  • num_channels (int): defines the number of channels that are used for the transfer
  • bytes_per_sample (int): defines the number of bytes per sample
  • bits_per_sample (int): defines the number of bits per sample
  • direction (Direction = Direction.Acquisition): Direction of the data transfer.
DataTransfer(card, *args, **kwargs)
 83    def __init__(self, card, *args, **kwargs) -> None:
 84        """
 85        Initialize the DataTransfer object with a card object and additional arguments
 86
 87        Parameters
 88        ----------
 89        card : Card
 90            the card object that is used for the data transfer
 91        *args : list
 92            list of additional arguments
 93        **kwargs : dict
 94            dictionary of additional keyword arguments
 95        """
 96        
 97        self.buffer_size = 0
 98        self.notify_size = 0
 99        self.num_channels = 0
100        self.bytes_per_sample = 0
101        self.bits_per_sample = 0
102
103        self.current_user_pos = 0
104
105        self._buffer_samples = 0
106        self._notify_samples = 0
107        self._memory_size = 0
108        self._c_buffer = None
109        self._buffer_alignment = 4096
110        self._np_buffer = None
111        self._bit_buffer = None
112        self._8bit_mode = False
113        self._12bit_mode = False
114        self._pre_trigger = 0
115        
116        super().__init__(card, *args, **kwargs)
117        self.buffer_type = SPCM_BUF_DATA
118        self._bytes_per_sample()
119        self._bits_per_sample()
120        self.num_channels = self.card.active_channels()
121
122        # Find out the direction of transfer
123        if self.function_type == SPCM_TYPE_AI or self.function_type == SPCM_TYPE_DI:
124            self.direction = Direction.Acquisition
125        elif self.function_type == SPCM_TYPE_AO or self.function_type == SPCM_TYPE_DO:
126            self.direction = Direction.Generation
127        else:
128            self.direction = Direction.Undefined

Initialize the DataTransfer object with a card object and additional arguments

Parameters
  • card (Card): the card object that is used for the data transfer
  • *args (list): list of additional arguments
  • **kwargs (dict): dictionary of additional keyword arguments
buffer_size: int = 0
notify_size: int = 0
direction: spcm_core.pyspcm.Direction = <Direction.Acquisition: 1>
buffer_type: int
num_channels: int = 0
bytes_per_sample: int = 0
bits_per_sample: int = 0
current_user_pos: int = 0
buffer: numpy.ndarray[tuple[int, ...], numpy.dtype[numpy.int64]]
131    @property
132    def buffer(self) -> npt.NDArray[np.int_]:
133        """
134        The numpy buffer object that interfaces the Card and can be written and read from
135        
136        Returns
137        -------
138        numpy array
139            the numpy buffer object with the following array index definition: 
140            `[channel, sample]`
141            or in case of multiple recording / replay:
142            `[segment, sample, channel]`
143        """
144        return self._np_buffer

The numpy buffer object that interfaces the Card and can be written and read from

Returns
  • numpy array: the numpy buffer object with the following array index definition: [channel, sample] or in case of multiple recording / replay: [segment, sample, channel]
bit_buffer: numpy.ndarray[tuple[int, ...], numpy.dtype[numpy.int64]]
154    @property
155    def bit_buffer(self) -> npt.NDArray[np.int_]:
156        """
157        The bit buffer object that interfaces the Card and can be written and read from
158        
159        Returns
160        -------
161        numpy array
162            with the buffer object where all the individual bits are now unpacked
163        """
164
165        # self._bit_buffer = self.unpackbits(self._np_buffer) # not a good solution
166        return self._bit_buffer

The bit buffer object that interfaces the Card and can be written and read from

Returns
  • numpy array: with the buffer object where all the individual bits are now unpacked
buffer_samples: int
176    @property
177    def buffer_samples(self) -> int:
178        """
179        The number of samples in the buffer
180        
181        Returns
182        -------
183        int
184            the number of samples in the buffer
185        """
186        return self._buffer_samples

The number of samples in the buffer

Returns
  • int: the number of samples in the buffer
def bytes_to_samples(self, num_bytes: int) -> int:
232    def bytes_to_samples(self, num_bytes : int) -> int:
233        """
234        Convert bytes to samples
235        
236        Parameters
237        ----------
238        bytes : int
239            the number of bytes
240        
241        Returns
242        -------
243        int
244            the number of samples
245        """
246
247        if self.bits_per_sample > 1:
248            num_samples = num_bytes // self.bytes_per_sample // self.num_channels
249        else:
250            num_samples = num_bytes // self.num_channels * 8
251        return num_samples

Convert bytes to samples

Parameters
  • bytes (int): the number of bytes
Returns
  • int: the number of samples
def samples_to_bytes(self, num_samples: int) -> int:
253    def samples_to_bytes(self, num_samples : int) -> int:
254        """
255        Convert samples to bytes
256        
257        Parameters
258        ----------
259        num_samples : int
260            the number of samples
261        
262        Returns
263        -------
264        int
265            the number of bytes
266        """
267
268        if self.bits_per_sample > 1:
269            num_bytes = num_samples * self.bytes_per_sample * self.num_channels
270        else:
271            num_bytes = num_samples * self.num_channels // 8
272        return num_bytes

Convert samples to bytes

Parameters
  • num_samples (int): the number of samples
Returns
  • int: the number of bytes
def notify_samples(self, notify_samples: int = None) -> int:
274    def notify_samples(self, notify_samples : int = None) -> int:
275        """
276        Set the number of samples to notify the user about
277        
278        Parameters
279        ----------
280        notify_samples : int | pint.Quantity
281            the number of samples to notify the user about
282        """
283
284        if notify_samples is not None:
285            notify_samples = UnitConversion.convert(notify_samples, units.Sa, int)
286            self._notify_samples = notify_samples
287            self.notify_size = self.samples_to_bytes(self._notify_samples)
288        return self._notify_samples

Set the number of samples to notify the user about

Parameters
  • notify_samples (int | pint.Quantity): the number of samples to notify the user about
def memory_size(self, memory_samples: int = None) -> int:
301    def memory_size(self, memory_samples : int = None) -> int:
302        """
303        Sets the memory size in samples per channel. The memory size setting must be set before transferring 
304        data to the card. (see register `SPC_MEMSIZE` in the manual)
305        
306        Parameters
307        ----------
308        memory_samples : int | pint.Quantity
309            the size of the memory in samples
310        
311        Returns
312        -------
313        int
314            the size of the memory in samples
315        """
316
317        if memory_samples is not None:
318            memory_samples = UnitConversion.convert(memory_samples, units.Sa, int)
319            self.card.set_i(SPC_MEMSIZE, memory_samples)
320        self._memory_size = self.card.get_i(SPC_MEMSIZE)
321        return self._memory_size

Sets the memory size in samples per channel. The memory size setting must be set before transferring data to the card. (see register SPC_MEMSIZE in the manual)

Parameters
  • memory_samples (int | pint.Quantity): the size of the memory in samples
Returns
  • int: the size of the memory in samples
def output_buffer_size(self, buffer_samples: int = None) -> int:
323    def output_buffer_size(self, buffer_samples : int = None) -> int:
324        """
325        Set the size of the output buffer (see register `SPC_DATA_OUTBUFSIZE` in the manual)
326        
327        Parameters
328        ----------
329        buffer_samples : int | pint.Quantity
330            the size of the output buffer in Bytes
331        
332        Returns
333        -------
334        int
335            the size of the output buffer in Samples
336        """
337
338        if buffer_samples is not None:
339            buffer_samples = UnitConversion.convert(buffer_samples, units.B, int)
340            buffer_size = self.samples_to_bytes(buffer_samples)
341            self.card.set_i(SPC_DATA_OUTBUFSIZE, buffer_size)
342        return self.bytes_to_samples(self.card.get_i(SPC_DATA_OUTBUFSIZE))

Set the size of the output buffer (see register SPC_DATA_OUTBUFSIZE in the manual)

Parameters
  • buffer_samples (int | pint.Quantity): the size of the output buffer in Bytes
Returns
  • int: the size of the output buffer in Samples
def loops(self, loops: int = None) -> int:
344    def loops(self, loops : int = None) -> int:
345        return self.card.loops(loops)
def pre_trigger(self, num_samples: int = None) -> int:
347    def pre_trigger(self, num_samples : int = None) -> int:
348        """
349        Set the number of pre trigger samples (see register `SPC_PRETRIGGER` in the manual)
350        
351        Parameters
352        ----------
353        num_samples : int | pint.Quantity
354            the number of pre trigger samples
355        
356        Returns
357        -------
358        int
359            the number of pre trigger samples
360        """
361
362        if num_samples is not None:
363            num_samples = UnitConversion.convert(num_samples, units.Sa, int)
364            self.card.set_i(SPC_PRETRIGGER, num_samples)
365        self._pre_trigger = self.card.get_i(SPC_PRETRIGGER)
366        return self._pre_trigger

Set the number of pre trigger samples (see register SPC_PRETRIGGER in the manual)

Parameters
  • num_samples (int | pint.Quantity): the number of pre trigger samples
Returns
  • int: the number of pre trigger samples
def post_trigger(self, num_samples: int = None, set_pre_trigger: bool = True) -> int:
368    def post_trigger(self, num_samples : int = None, set_pre_trigger : bool = True) -> int:
369        """
370        Set the number of post trigger samples (see register `SPC_POSTTRIGGER` in the manual)
371        
372        Parameters
373        ----------
374        num_samples : int | pint.Quantity
375            the number of post trigger samples
376        set_pre_trigger : bool = True
377            if True, the pre trigger samples are set to the memory size minus the post trigger samples.
378        
379        Returns
380        -------
381        int
382            the number of post trigger samples
383        """
384
385        if set_pre_trigger and self._memory_size < num_samples:
386            raise ValueError(f"The number of post trigger samples needs to be smaller than the total number of samples: {self._memory_size} < {num_samples}")
387        if num_samples is not None:
388            num_samples = UnitConversion.convert(num_samples, units.Sa, int)
389            self.card.set_i(SPC_POSTTRIGGER, num_samples)
390        post_trigger = self.card.get_i(SPC_POSTTRIGGER)
391        if set_pre_trigger:
392            self._pre_trigger = self._memory_size - post_trigger
393        return post_trigger

Set the number of post trigger samples (see register SPC_POSTTRIGGER in the manual)

Parameters
  • num_samples (int | pint.Quantity): the number of post trigger samples
  • set_pre_trigger (bool = True): if True, the pre trigger samples are set to the memory size minus the post trigger samples.
Returns
  • int: the number of post trigger samples
def allocate_buffer(self, num_samples: int, no_reshape=False) -> None:
395    def allocate_buffer(self, num_samples : int, no_reshape = False) -> None:
396        """
397        Memory allocation for the buffer that is used for communicating with the card
398
399        Parameters
400        ----------
401        num_samples : int | pint.Quantity = None
402            use the number of samples an get the number of active channels and bytes per samples directly from the card
403        no_reshape : bool = False
404            if True, the buffer is not reshaped to the number of channels. This is useful for digital cards where the data is packed in a single array. 
405            As well as for 12bit cards where three data points are packed in four bytes or two 16-bit samples.
406        """
407
408        self.buffer_samples = UnitConversion.convert(num_samples, units.Sa, int)
409        no_reshape |= self._12bit_mode | self.bits_per_sample == 1
410        self.buffer = self._allocate_buffer(self.buffer_samples, no_reshape, self.num_channels)
411        if self.bits_per_sample == 1:
412            self.unpackbits() # allocate the bit buffer for digital cards

Memory allocation for the buffer that is used for communicating with the card

Parameters
  • num_samples (int | pint.Quantity = None): use the number of samples an get the number of active channels and bytes per samples directly from the card
  • no_reshape (bool = False): if True, the buffer is not reshaped to the number of channels. This is useful for digital cards where the data is packed in a single array. As well as for 12bit cards where three data points are packed in four bytes or two 16-bit samples.
def start_buffer_transfer( self, *args, buffer_type=1000, direction=None, notify_samples=None, transfer_offset=None, transfer_length=None, exception_num_samples=False) -> None:
448    def start_buffer_transfer(self, *args, buffer_type=SPCM_BUF_DATA, direction=None, notify_samples=None, transfer_offset=None, transfer_length=None, exception_num_samples=False) -> None:
449        """
450        Start the transfer of the data to or from the card  (see the API function `spcm_dwDefTransfer_i64` in the manual)
451        
452        Parameters
453        ----------
454        *args : list
455            list of additonal arguments that are added as flags to the start dma command
456        buffer_type : int
457            the type of buffer that is used for the transfer
458        direction : int
459            the direction of the transfer
460        notify_samples : int
461            the number of samples to notify the user about
462        transfer_offset : int
463            the offset of the transfer
464        transfer_length : int
465            the length of the transfer
466        exception_num_samples : bool
467            if True, an exception is raised if the number of samples is not a multiple of the notify samples. The automatic buffer handling only works with the number of samples being a multiple of the notify samples.
468
469        Raises
470        ------
471        SpcmException
472        """
473
474        self.notify_samples(UnitConversion.convert(notify_samples, units.Sa, int))
475        transfer_offset = UnitConversion.convert(transfer_offset, units.Sa, int)
476        transfer_length = UnitConversion.convert(transfer_length, units.Sa, int)
477
478        if transfer_length is not None:
479            self.buffer_samples = transfer_length
480
481        if self.buffer is None: 
482            raise SpcmException(text="No buffer defined for transfer")
483        if buffer_type: 
484            self.buffer_type = buffer_type
485        if direction is None:
486            if self.direction == Direction.Acquisition:
487                direction = SPCM_DIR_CARDTOPC
488            elif self.direction == Direction.Generation:
489                direction = SPCM_DIR_PCTOCARD
490            else:
491                raise SpcmException(text="Please define a direction for transfer (SPCM_DIR_CARDTOPC or SPCM_DIR_PCTOCARD)")
492        
493        if self._notify_samples != 0 and np.remainder(self.buffer_samples, self._notify_samples) and exception_num_samples:
494            raise SpcmException("The number of samples needs to be a multiple of the notify samples.")
495
496        if transfer_offset:
497            transfer_offset_bytes = self.samples_to_bytes(transfer_offset)
498        else:
499            transfer_offset_bytes = 0
500        
501        # we define the buffer for transfer
502        self.card._print("Starting the DMA transfer and waiting until data is in board memory")
503        self._c_buffer = self.buffer.ctypes.data_as(c_void_p)
504        self.card._check_error(spcm_dwDefTransfer_i64(self.card._handle, self.buffer_type, direction, self.notify_size, self._c_buffer, transfer_offset_bytes, self.buffer_size))
505        
506        # Execute additional commands if available
507        if args:
508            cmd = 0
509            for arg in args:
510                cmd |= arg
511            self.card.cmd(cmd)
512            self.card._print("... data transfer started")

Start the transfer of the data to or from the card (see the API function spcm_dwDefTransfer_i64 in the manual)

Parameters
  • *args (list): list of additonal arguments that are added as flags to the start dma command
  • buffer_type (int): the type of buffer that is used for the transfer
  • direction (int): the direction of the transfer
  • notify_samples (int): the number of samples to notify the user about
  • transfer_offset (int): the offset of the transfer
  • transfer_length (int): the length of the transfer
  • exception_num_samples (bool): if True, an exception is raised if the number of samples is not a multiple of the notify samples. The automatic buffer handling only works with the number of samples being a multiple of the notify samples.
Raises
  • SpcmException
def duration( self, duration: pint.registry.Quantity, pre_trigger_duration: pint.registry.Quantity = None, post_trigger_duration: pint.registry.Quantity = None) -> None:
514    def duration(self, duration : pint.Quantity, pre_trigger_duration : pint.Quantity = None, post_trigger_duration : pint.Quantity = None) -> None:
515        """
516        Set the duration of the data transfer
517        
518        Parameters
519        ----------
520        duration : pint.Quantity
521            the duration of the data transfer
522        pre_trigger_duration : pint.Quantity = None
523            the duration before the trigger event
524        post_trigger_duration : pint.Quantity = None
525            the duration after the trigger event
526        
527        Returns
528        -------
529        pint.Quantity
530            the duration of the data transfer
531        """
532
533        if pre_trigger_duration is None and post_trigger_duration is None:
534            raise ValueError("Please define either pre_trigger_duration or post_trigger_duration")
535
536        memsize_min = self.card.get_i(SPC_AVAILMEMSIZE_MIN)
537        memsize_max = self.card.get_i(SPC_AVAILMEMSIZE_MAX)
538        memsize_stp = self.card.get_i(SPC_AVAILMEMSIZE_STEP)
539        num_samples = (duration * self._sample_rate()).to_base_units().magnitude
540        num_samples = np.ceil(num_samples / memsize_stp) * memsize_stp
541        num_samples = np.clip(num_samples, memsize_min, memsize_max)
542        num_samples = int(num_samples)
543        self.memory_size(num_samples)
544        self.allocate_buffer(num_samples)
545        if pre_trigger_duration is not None:
546            pre_min = self.card.get_i(SPC_AVAILPRETRIGGER_MIN)
547            pre_max = self.card.get_i(SPC_AVAILPRETRIGGER_MAX)
548            pre_stp = self.card.get_i(SPC_AVAILPRETRIGGER_STEP)
549            pre_samples = (pre_trigger_duration * self._sample_rate()).to_base_units().magnitude
550            pre_samples = np.ceil(pre_samples / pre_stp) * pre_stp
551            pre_samples = np.clip(pre_samples, pre_min, pre_max)
552            pre_samples = int(post_samples)
553            self.post_trigger(post_samples)
554        if post_trigger_duration is not None:
555            post_min = self.card.get_i(SPC_AVAILPOSTTRIGGER_MIN)
556            post_max = self.card.get_i(SPC_AVAILPOSTTRIGGER_MAX)
557            post_stp = self.card.get_i(SPC_AVAILPOSTTRIGGER_STEP)
558            post_samples = (post_trigger_duration * self._sample_rate()).to_base_units().magnitude
559            post_samples = np.ceil(post_samples / post_stp) * post_stp
560            post_samples = np.clip(post_samples, post_min, post_max)
561            post_samples = int(post_samples)
562            self.post_trigger(post_samples)
563        return num_samples, post_samples

Set the duration of the data transfer

Parameters
  • duration (pint.Quantity): the duration of the data transfer
  • pre_trigger_duration (pint.Quantity = None): the duration before the trigger event
  • post_trigger_duration (pint.Quantity = None): the duration after the trigger event
Returns
  • pint.Quantity: the duration of the data transfer
def time_data( self, total_num_samples: int = None, return_units=<Unit('second')>) -> numpy.ndarray[tuple[int, ...], numpy.dtype[+_ScalarType_co]]:
565    def time_data(self, total_num_samples : int = None, return_units = units.s) -> npt.NDArray:
566        """
567        Get the time array for the data buffer
568
569        Parameters
570        ----------
571        total_num_samples : int | pint.Quantity
572            the total number of samples
573        return_units : pint.Quantity
574            the units that the time should be converted to
575        
576        Returns
577        -------
578        numpy array
579            the time array
580        """
581
582        if total_num_samples is None:
583            total_num_samples = self._buffer_samples
584        total_num_samples = UnitConversion.convert(total_num_samples, units.Sa, int)
585        pre_trigger = UnitConversion.convert(self._pre_trigger, units.Sa, int)
586        return self.convert_time((np.arange(total_num_samples) - pre_trigger)).to(return_units)

Get the time array for the data buffer

Parameters
  • total_num_samples (int | pint.Quantity): the total number of samples
  • return_units (pint.Quantity): the units that the time should be converted to
Returns
  • numpy array: the time array
def convert_time(self, time, return_units=<Unit('second')>):
588    def convert_time(self, time, return_units = units.s):
589        """
590        Convert a time to the units of the card sample rate
591        
592        Parameters
593        ----------
594        time : numpy array
595            the time array with integers that should be converted
596        return_units : numpy array with pint.Quantity
597            the units that the time should be converted to
598        
599        Returns
600        -------
601        pint.Quantity
602            the converted time
603        """
604
605        sample_rate = self._sample_rate()
606        return (time / sample_rate).to(return_units)

Convert a time to the units of the card sample rate

Parameters
  • time (numpy array): the time array with integers that should be converted
  • return_units (numpy array with pint.Quantity): the units that the time should be converted to
Returns
  • pint.Quantity: the converted time
def unpack_12bit_buffer( self, data: numpy.ndarray[tuple[int, ...], numpy.dtype[numpy.int64]] = None) -> numpy.ndarray[tuple[int, ...], numpy.dtype[numpy.int64]]:
608    def unpack_12bit_buffer(self, data : npt.NDArray[np.int_] = None) -> npt.NDArray[np.int_]:
609        """
610        Unpacks the 12-bit packed data to 16-bit data
611        
612        Parameters
613        ----------
614        data : numpy array
615            the packed data
616
617        Returns
618        -------
619        numpy array
620            the unpacked 16bit buffer
621        """
622
623        if not self._12bit_mode:
624            raise SpcmException("The card is not in 12bit packed mode")
625        
626        if data is None:
627            data = self.buffer
628
629        fst_int8, mid_int8, lst_int8 = np.reshape(data, (data.shape[0] // 3, 3)).astype(np.int16).T
630        nibble_h = (mid_int8 >> 0) & 0x0F
631        nibble_m = (fst_int8 >> 4) & 0x0F
632        nibble_l = (fst_int8 >> 0) & 0x0F
633        fst_int12 = ((nibble_h << 12) >> 4) | (nibble_m << 4) | (nibble_l << 0)
634        nibble_h = (lst_int8 >> 4) & 0x0F
635        nibble_m = (lst_int8 >> 0) & 0x0F
636        nibble_l = (mid_int8 >> 4) & 0x0F
637        snd_int12 = ((nibble_h << 12) >> 4) | (nibble_m << 4) | (nibble_l << 0)
638        data_int12 = np.concatenate((fst_int12[:, None], snd_int12[:, None]), axis=1).reshape((-1,))
639        data_int12 = data_int12.reshape((self.num_channels, self._buffer_samples), order='F')
640        return data_int12

Unpacks the 12-bit packed data to 16-bit data

Parameters
  • data (numpy array): the packed data
Returns
  • numpy array: the unpacked 16bit buffer
def unpackbits( self, data: numpy.ndarray[tuple[int, ...], numpy.dtype[numpy.int64]] = None) -> numpy.ndarray[tuple[int, ...], numpy.dtype[numpy.int64]]:
642    def unpackbits(self, data : npt.NDArray[np.int_] = None) -> npt.NDArray[np.int_]:
643        """
644        Unpack the buffer to bits
645
646        Parameters
647        ----------
648        data : numpy array | None = None
649            the packed data
650
651        Returns
652        -------
653        numpy array
654            the unpacked buffer
655        """
656
657        if data is None:
658            data = self.buffer
659        dshape = list(data.shape)
660        return_data = data.reshape([-1, 1])
661        num_bits = return_data.dtype.itemsize * 8
662        mask = 2**np.arange(num_bits, dtype=return_data.dtype).reshape([1, num_bits])
663        self.bit_buffer = (return_data & mask).astype(np.bool).astype(np.uint8).reshape(dshape + [num_bits])
664        return self.bit_buffer

Unpack the buffer to bits

Parameters
  • data (numpy array | None = None): the packed data
Returns
  • numpy array: the unpacked buffer
def packbits(self) -> None:
666    def packbits(self) -> None:
667        """
668        Pack the self.buffer from the self.bit_buffer
669        """
670
671        self.buffer[:] = np.packbits(self._bit_buffer, axis=-1, bitorder='little').view(self.buffer.dtype).reshape(self.buffer.shape)

Pack the self.buffer from the self.bit_buffer

def tofile(self, filename: str, buffer=None, **kwargs) -> None:
673    def tofile(self, filename : str, buffer = None, **kwargs) -> None:
674        """
675        Export the buffer to a file. The file format is determined by the file extension
676        Supported file formats are: 
677        * .bin: raw binary file
678        * .csv: comma-separated values file
679        * .npy: numpy binary file
680        * .npz: compressed numpy binary file
681        * .txt: whitespace-delimited text file
682        * .h5: hdf5 file format
683
684        Parameters
685        ----------
686        filename : str
687            the name of the file that the buffer should be exported to
688        
689        Raises
690        ------
691        ImportError
692            if the file format is not supported
693        """
694
695        if buffer is None:
696            buffer = self.buffer
697        file_path = Path(filename)
698        if file_path.suffix == '.bin':
699            buffer.tofile(file_path)
700        elif file_path.suffix == '.csv':
701            delimiter = kwargs.get('delimiter', ',')
702            np.savetxt(file_path, buffer, delimiter=delimiter)
703        elif file_path.suffix == '.npy':
704            np.save(file_path, buffer)
705        elif file_path.suffix == '.npz':
706            np.savez_compressed(file_path, buffer)
707        elif file_path.suffix == '.txt':
708            np.savetxt(file_path, buffer, fmt='%d')
709        elif file_path.suffix == '.h5' or file_path.suffix == '.hdf5':
710            import h5py
711            with h5py.File(file_path, 'w') as f:
712                f.create_dataset('data', data=buffer)
713        else:
714            raise ImportError("File format not supported")

Export the buffer to a file. The file format is determined by the file extension Supported file formats are:

  • .bin: raw binary file
  • .csv: comma-separated values file
  • .npy: numpy binary file
  • .npz: compressed numpy binary file
  • .txt: whitespace-delimited text file
  • .h5: hdf5 file format
Parameters
  • filename (str): the name of the file that the buffer should be exported to
Raises
  • ImportError: if the file format is not supported
def fromfile( self, filename: str, in_buffer: bool = True, **kwargs) -> numpy.ndarray[tuple[int, ...], numpy.dtype[numpy.int64]]:
716    def fromfile(self, filename : str, in_buffer : bool = True, **kwargs) -> npt.NDArray[np.int_]:
717        """
718        Import the buffer from a file. The file format is determined by the file extension
719        Supported file formats are: 
720        * .bin: raw binary file
721        * .csv: comma-separated values file
722        * .npy: numpy binary file
723        * .npz: compressed numpy binary file
724        * .txt: whitespace-delimited text file
725        * .h5: hdf5 file format
726
727        Parameters
728        ----------
729        filename : str
730            the name of the file that the buffer should be imported from
731        
732        Raises
733        ------
734        ImportError
735            if the file format is not supported
736        """
737
738        file_path = Path(filename)
739        if file_path.suffix == '.bin':
740            dtype = kwargs.get('dtype', self.numpy_type())
741            shape = kwargs.get('shape', (self.num_channels, self.buffer_size // self.num_channels))
742            buffer = np.fromfile(file_path, dtype=dtype)
743            loaded_data = buffer.reshape(shape, order='C')
744        elif file_path.suffix == '.csv':
745            delimiter = kwargs.get('delimiter', ',')
746            loaded_data = np.loadtxt(file_path, delimiter=delimiter)
747        elif file_path.suffix == '.npy':
748            loaded_data = np.load(file_path)
749        elif file_path.suffix == '.npz':
750            data = np.load(file_path)
751            loaded_data = data['arr_0']
752        elif file_path.suffix == '.txt':
753            loaded_data = np.loadtxt(file_path)
754        elif file_path.suffix == '.h5' or file_path.suffix == '.hdf5':
755            import h5py
756            with h5py.File(file_path, 'r') as f:
757                loaded_data = f['data'][()]
758        else:
759            raise ImportError("File format not supported")
760        
761        if in_buffer:
762            self.buffer[:] = loaded_data
763        return loaded_data

Import the buffer from a file. The file format is determined by the file extension Supported file formats are:

  • .bin: raw binary file
  • .csv: comma-separated values file
  • .npy: numpy binary file
  • .npz: compressed numpy binary file
  • .txt: whitespace-delimited text file
  • .h5: hdf5 file format
Parameters
  • filename (str): the name of the file that the buffer should be imported from
Raises
  • ImportError: if the file format is not supported
def avail_card_len(self, available_samples: int = 0) -> None:
766    def avail_card_len(self, available_samples : int = 0) -> None:
767        """
768        Set the amount of data that has been read out of the data buffer (see register `SPC_DATA_AVAIL_CARD_LEN` in the manual)
769
770        Parameters
771        ----------
772        available_samples : int | pint.Quantity
773            the amount of data that is available for reading
774        """
775
776        available_samples = UnitConversion.convert(available_samples, units.Sa, int)
777        # print(available_samples, self.bytes_per_sample, self.num_channels)
778        available_bytes = self.samples_to_bytes(available_samples)
779        self.card.set_i(SPC_DATA_AVAIL_CARD_LEN, available_bytes)

Set the amount of data that has been read out of the data buffer (see register SPC_DATA_AVAIL_CARD_LEN in the manual)

Parameters
  • available_samples (int | pint.Quantity): the amount of data that is available for reading
def avail_user_pos(self, in_bytes: bool = False) -> int:
781    def avail_user_pos(self, in_bytes : bool = False) -> int:
782        """
783        Get the current position of the pointer in the data buffer (see register `SPC_DATA_AVAIL_USER_POS` in the manual)
784
785        Parameters
786        ----------
787        in_bytes : bool
788            if True, the position is returned in bytes
789
790        Returns
791        -------
792        int
793            pointer position
794        """
795
796        self.current_user_pos = self.card.get_i(SPC_DATA_AVAIL_USER_POS)
797        if not in_bytes:
798            self.current_user_pos = self.bytes_to_samples(self.current_user_pos)
799        return self.current_user_pos

Get the current position of the pointer in the data buffer (see register SPC_DATA_AVAIL_USER_POS in the manual)

Parameters
  • in_bytes (bool): if True, the position is returned in bytes
Returns
  • int: pointer position
def avail_user_len(self, in_bytes: bool = False) -> int:
801    def avail_user_len(self, in_bytes : bool = False) -> int:
802        """
803        Get the current length of the data in the data buffer (see register `SPC_DATA_AVAIL_USER_LEN` in the manual)
804
805        Parameters
806        ----------
807        in_bytes : bool
808            if True, the length is returned in bytes
809
810        Returns
811        -------
812        int
813            data length available
814        """
815
816        user_len = self.card.get_i(SPC_DATA_AVAIL_USER_LEN)
817        if not in_bytes:
818            user_len = self.bytes_to_samples(user_len)
819        return user_len

Get the current length of the data in the data buffer (see register SPC_DATA_AVAIL_USER_LEN in the manual)

Parameters
  • in_bytes (bool): if True, the length is returned in bytes
Returns
  • int: data length available
def fill_size_promille(self, return_unit=None) -> int:
821    def fill_size_promille(self, return_unit = None) -> int:
822        """
823        Get the fill size of the data buffer (see register `SPC_FILLSIZEPROMILLE` in the manual)
824
825        Returns
826        -------
827        int
828            fill size
829        """
830
831        return_value = self.card.get_i(SPC_FILLSIZEPROMILLE)
832        if return_unit is not None: return_value = UnitConversion.to_unit(return_value * units.promille, return_unit)
833        return return_value

Get the fill size of the data buffer (see register SPC_FILLSIZEPROMILLE in the manual)

Returns
  • int: fill size
def wait_dma(self) -> None:
835    def wait_dma(self) -> None:
836        """
837        Wait for the DMA transfer to finish (see register `M2CMD_DATA_WAITDMA` in the manual)
838        """
839        
840        self.card.cmd(M2CMD_DATA_WAITDMA)

Wait for the DMA transfer to finish (see register M2CMD_DATA_WAITDMA in the manual)

def wait(self) -> None:
835    def wait_dma(self) -> None:
836        """
837        Wait for the DMA transfer to finish (see register `M2CMD_DATA_WAITDMA` in the manual)
838        """
839        
840        self.card.cmd(M2CMD_DATA_WAITDMA)

Wait for the DMA transfer to finish (see register M2CMD_DATA_WAITDMA in the manual)

def numpy_type(self) -> numpy.ndarray[tuple[int, ...], numpy.dtype[numpy.int64]]:
843    def numpy_type(self) -> npt.NDArray[np.int_]:
844        """
845        Get the type of numpy data from number of bytes
846
847        Returns
848        -------
849        numpy data type
850            the type of data that is used by the card
851        """
852
853        if self._8bit_mode:
854            return np.uint8
855        if self._12bit_mode:
856            return np.int8
857        if self.bits_per_sample == 1:
858            if self.num_channels <= 8:
859                return np.uint8
860            elif self.num_channels <= 16:
861                return np.uint16
862            elif self.num_channels <= 32:
863                return np.uint32
864            return np.uint64
865        if self.bits_per_sample <= 8:
866            return np.int8
867        elif self.bits_per_sample <= 16:
868            return np.int16
869        elif self.bits_per_sample <= 32:
870            return np.int32
871        return np.int64

Get the type of numpy data from number of bytes

Returns
  • numpy data type: the type of data that is used by the card
def data_conversion(self, mode: int = None) -> int:
874    def data_conversion(self, mode : int = None) -> int:
875        """
876        Set the data conversion mode (see register `SPC_DATACONVERSION` in the manual)
877        
878        Parameters
879        ----------
880        mode : int
881            the data conversion mode
882        """
883
884        if mode is not None:
885            self.card.set_i(SPC_DATACONVERSION, mode)
886        mode = self.card.get_i(SPC_DATACONVERSION)
887        self._8bit_mode = (mode == SPCM_DC_12BIT_TO_8BIT or mode == SPCM_DC_14BIT_TO_8BIT or mode == SPCM_DC_16BIT_TO_8BIT)
888        self._12bit_mode = (mode == SPCM_DC_12BIT_TO_12BITPACKED)
889        self._bits_per_sample()
890        self._bytes_per_sample()
891        return mode

Set the data conversion mode (see register SPC_DATACONVERSION in the manual)

Parameters
  • mode (int): the data conversion mode
def avail_data_conversion(self) -> int:
893    def avail_data_conversion(self) -> int:
894        """
895        Get the available data conversion modes (see register `SPC_AVAILDATACONVERSION` in the manual)
896
897        Returns
898        -------
899        int
900            the available data conversion modes
901        """
902        return self.card.get_i(SPC_AVAILDATACONVERSION)

Get the available data conversion modes (see register SPC_AVAILDATACONVERSION in the manual)

Returns
  • int: the available data conversion modes
iterator_index = 0
def verbose(self, verbose: bool = None) -> bool:
914    def verbose(self, verbose : bool = None) -> bool:
915        """
916        Set or get the verbose mode for the data transfer
917
918        Parameters
919        ----------
920        verbose : bool = None
921            the verbose mode
922        """
923
924        if verbose is not None:
925            self._verbose = verbose
926        return self._verbose

Set or get the verbose mode for the data transfer

Parameters
  • verbose (bool = None): the verbose mode
def to_transfer_samples(self, samples) -> None:
928    def to_transfer_samples(self, samples) -> None:
929        """
930        This method sets the number of samples to transfer
931
932        Parameters
933        ----------
934        samples : int | pint.Quantity
935            the number of samples to transfer
936        """
937
938        samples = UnitConversion.convert(samples, units.Sa, int)
939        self._to_transfer_samples = samples

This method sets the number of samples to transfer

Parameters
  • samples (int | pint.Quantity): the number of samples to transfer
def polling(self, polling: bool = True, timer: float = 0.01) -> None:
954    def polling(self, polling : bool = True, timer : float = 0.01) -> None:
955        """
956        Set the polling mode for the data transfer otherwise wait_dma() is used
957
958        Parameters
959        ----------
960        polling : bool
961            True to enable polling, False to disable polling
962        timer : float | pint.Quantity
963            the polling timer in seconds
964        """
965
966        self._polling = polling
967        self._polling_timer = UnitConversion.convert(timer, units.s, float, rounding=None)

Set the polling mode for the data transfer otherwise wait_dma() is used

Parameters
  • polling (bool): True to enable polling, False to disable polling
  • timer (float | pint.Quantity): the polling timer in seconds
def auto_avail_card_len(self, value: bool = None) -> bool:
970    def auto_avail_card_len(self, value : bool = None) -> bool:
971        """
972        Enable or disable the automatic sending of the number of samples that the card can now use for sample data transfer again
973
974        Parameters
975        ----------
976        value : bool = None
977            True to enable, False to disable and None to get the current status
978
979        Returns
980        -------
981        bool
982            the current status
983        """
984        if value is not None:
985            self._auto_avail_card_len = value
986        return self._auto_avail_card_len

Enable or disable the automatic sending of the number of samples that the card can now use for sample data transfer again

Parameters
  • value (bool = None): True to enable, False to disable and None to get the current status
Returns
  • bool: the current status
def flush(self):
1039    def flush(self):
1040        """
1041        This method is used to tell the card that a notify size of data is freed up after reading (acquisition) or written to (generation)
1042        """
1043        self.avail_card_len(self._notify_samples)

This method is used to tell the card that a notify size of data is freed up after reading (acquisition) or written to (generation)

class DDS(spcm.CardFunctionality):
 402class DDS(CardFunctionality):
 403    """a higher-level abstraction of the SpcmCardFunctionality class to implement DDS functionality
 404
 405    The DDS firmware allows the user a certain maximum number of dds cores, that 
 406    each on it's own generates a sine wave with the following parameters:
 407    * static parameters:
 408        + frequency
 409        + amplitude
 410        + phase
 411    * dynamic parameters:
 412        + frequency_slope
 413            changes the active frequency of the dds core with a linear slope
 414        + amplitude_slope
 415            changes the active amplitude of the dds core with a linear slope
 416    Each of these cores can either be added together and outputted, or specific groups
 417    of cores can be added together and outputted on a specific hardware output channel.
 418    Furthermore, specific dds cores can be connected to input parameters of another dds core.
 419
 420    For more information about what setups are available, please have a look at the user manual
 421    for your specific card.
 422
 423    Commands
 424    ---------
 425    The DDS functionality is controlled through commands that are listed and then written to the card.
 426    These written lists of commands are collected in a shadow register and are transferred to 
 427    the active register when a trigger is received.
 428    
 429    There are three different trigger sources, that can be set with the method 'trg_source()':
 430    * SPCM_DDS_TRG_SRC_NONE  = 0
 431        no triggers are generated and the commands are only transfered to the active register
 432        when a exec_now command is send
 433    * SPCM_DDS_TRG_SRC_TIMER = 1
 434        the triggers are generated on a timed grid with a period that can be set by the 
 435        method 'trg_timer()'
 436    * SPCM_DDS_TRG_SRC_CARD  = 2
 437        the triggers come from the card internal trigger logic (for more information, 
 438        see our product's user manual on how to setup the different triggers). In the DDS-mode
 439        multiple triggers can be processed, as with the mode SPC_STD_REP_SINGLERESTART.
 440    
 441    Note
 442    ----
 443    also the trigger source setting happens when a trigger comes. Hence a change of
 444    the trigger mode only happens after an 'arm()' command was send and an internal trigger was
 445    received.
 446 
 447    """
 448
 449    cores : list[DDSCore] = []
 450    channels : Channels = None
 451
 452    check_features : bool = False
 453    no_units : bool = False
 454    core_type = DDSCore
 455
 456    _current_core : int = -1
 457    _channel_from_core : dict[int, int] = {}
 458
 459    def __init__(self, *args, **kwargs) -> None:
 460        super().__init__(*args, **kwargs)
 461        self.channels = kwargs.get("channels", None)
 462        self.check_features = kwargs.get("check_features", False)
 463        self.no_units = kwargs.get("no_units", False)
 464        self.cores = []
 465
 466        if self.no_units:
 467            self.core_type = DDSBareCore
 468        else:
 469            self.core_type = DDSCore
 470
 471        self.load_cores()
 472        # Check if DDS feature is installed
 473        if self.check_features:
 474            features = self.card.ext_features()
 475            if not ((features & SPCM_FEAT_EXTFW_DDS20) or (features & SPCM_FEAT_EXTFW_DDS50)):
 476                raise SpcmException("The DDS feature is not installed on the card")
 477    
 478    def load_cores(self):
 479        """
 480        load the cores of the DDS functionality
 481        """
 482
 483        self.cores = []
 484        num_cores = self.num_cores()
 485        
 486        if self.channels is not None:
 487            for channel in self.channels:
 488                cores_on_channel = self.get_cores_on_channel(channel.index)
 489                for core in range(num_cores):
 490                    if cores_on_channel & (1 << core):
 491                        self._channel_from_core[core] = channel
 492        
 493        for core in range(num_cores):
 494            if core in self._channel_from_core:
 495                self.cores.append(self.core_type(core, self, channel=self._channel_from_core[core]))
 496            else:
 497                self.cores.append(self.core_type(core, self))
 498        
 499    def __len__(self) -> int:
 500        """
 501        get the number of cores
 502
 503        Returns
 504        -------
 505        int
 506            the number of cores
 507        """
 508        return len(self.cores)
 509    
 510    def __iter__(self):
 511        """
 512        make the class iterable
 513
 514        Returns
 515        -------
 516        self
 517        """
 518        return self
 519    
 520    def __next__(self):
 521        """
 522        get the next core
 523
 524        Returns
 525        -------
 526        DDSCore
 527            the next core
 528        """
 529
 530        self._current_core += 1
 531        if self._current_core < len(self.cores):
 532            return self.cores[self._current_core]
 533        else:
 534            self._current_core = -1
 535            raise StopIteration
 536    
 537    def __getitem__(self, index : int) -> DDSCore:
 538        """
 539        get a specific core
 540
 541        Parameters
 542        ----------
 543        index : int
 544            the index of the core
 545
 546        Returns
 547        -------
 548        DDSCore
 549            the specific core
 550        """
 551
 552        return self.cores[index]
 553
 554    def set_i(self, reg : int, value : int) -> None:
 555        """
 556        set an integer value to a register
 557
 558        Parameters
 559        ----------
 560        reg : int
 561            the register to be changed
 562        value : int
 563            the value to be set
 564        
 565        Raises
 566        ------
 567        SpcmException
 568            if the command list is full
 569        """
 570
 571        self.card.set_i(reg, value)
 572    
 573    def set_d(self, reg : int, value : float) -> None:
 574        """
 575        set a double value to a register
 576
 577        Parameters
 578        ----------
 579        reg : int
 580            the register to be changed
 581        value : float
 582            the value to be set
 583        """
 584    
 585        self.card.set_d(reg, value)
 586
 587    def reset(self) -> None:
 588        """
 589        Resets the DDS specific part of the firmware (see register `SPC_DDS_CMD` in the manual)
 590        """
 591
 592        self.cmd(SPCM_DDS_CMD_RESET)
 593    
 594    # DDS information
 595    def num_cores(self) -> int:
 596        """
 597        get the total num of available cores on the card. (see register `SPC_DDS_NUM_CORES` in the manual)
 598
 599        Returns
 600        -------
 601        int
 602            the available number of dds cores
 603        """
 604        return self.card.get_i(SPC_DDS_NUM_CORES)
 605    
 606    def queue_cmd_max(self):
 607        """
 608        get the total number of commands that can be hold by the queue. (see register `SPC_DDS_QUEUE_CMD_MAX` in the manual)
 609
 610        Returns
 611        -------
 612        int
 613            the total number of commands
 614        """
 615        return self.card.get_i(SPC_DDS_QUEUE_CMD_MAX)
 616    
 617    def queue_cmd_count(self):
 618        """
 619        get the current number of commands that are in the queue. (see register `SPC_DDS_QUEUE_CMD_COUNT` in the manual)
 620
 621        Returns
 622        -------
 623        int
 624            the current number of commands
 625        """
 626        return self.card.get_i(SPC_DDS_QUEUE_CMD_COUNT)
 627    
 628    def status(self):
 629        return self.card.get_i(SPC_DDS_STATUS)
 630
 631    # DDS setup settings
 632    def data_transfer_mode(self, mode : int) -> None:
 633        """
 634        set the data transfer mode for the DDS functionality (see register `SPC_DDS_DATA_TRANSFER_MODE` in the manual)
 635
 636        Parameters
 637        ----------
 638        mode : int
 639            the data transfer mode:
 640            * SPCM_DDS_DTM_SINGLE = 0
 641                the data is transferred using single commands (with lower latency)
 642            * SPCM_DDS_DTM_DMA = 1
 643                the data is transferred using DMA (with higher bandwidth)
 644        """
 645
 646        self._dtm = mode
 647        self.set_i(SPC_DDS_DATA_TRANSFER_MODE, mode)
 648    
 649    def get_data_transfer_mode(self) -> int:
 650        """
 651        get the data transfer mode for the DDS functionality (see register `SPC_DDS_DATA_TRANSFER_MODE` in the manual)
 652
 653        Returns
 654        -------
 655        int
 656            the data transfer mode:
 657            * SPCM_DDS_DTM_SINGLE = 0
 658                the data is transferred using single commands (with lower latency)
 659            * SPCM_DDS_DTM_DMA = 1
 660                the data is transferred using DMA (with higher bandwidth)
 661        """
 662
 663        self._dtm = self.card.get_i(SPC_DDS_DATA_TRANSFER_MODE)
 664        return self._dtm
 665    
 666    def phase_behaviour(self, behaviour : int) -> None:
 667        """
 668        set the phase behaviour of the DDS cores (see register `SPC_DDS_PHASE_BEHAVIOUR` in the manual)
 669
 670        Parameters
 671        ----------
 672        behaviour : int
 673            the phase behaviour
 674        """
 675
 676        self.set_i(SPC_DDS_PHASE_BEHAVIOUR, behaviour)
 677    
 678    def get_phase_behaviour(self) -> int:
 679        """
 680        get the phase behaviour of the DDS cores (see register `SPC_DDS_PHASE_BEHAVIOUR` in the manual)
 681
 682        Returns
 683        -------
 684        int
 685            the phase behaviour
 686        """
 687
 688        return self.card.get_i(SPC_DDS_PHASE_BEHAVIOUR)
 689
 690    def cores_on_channel(self, channel : int, *args) -> None:
 691        """
 692        setup the cores that are connected to a specific channel (see register `SPC_DDS_CORES_ON_CH0` in the manual)
 693
 694        Parameters
 695        ----------
 696        channel : int
 697            the channel number
 698        *args : int
 699            the cores that are connected to the channel
 700        
 701        TODO: change the channel associated with each core
 702        """
 703
 704        mask = 0
 705        for core in args:
 706            mask |= core
 707        self.set_i(SPC_DDS_CORES_ON_CH0 + channel, mask)
 708    
 709    def get_cores_on_channel(self, channel : int) -> int:
 710        """
 711        get the cores that are connected to a specific channel (see register `SPC_DDS_CORES_ON_CH0` in the manual)
 712
 713        Parameters
 714        ----------
 715        channel : int
 716            the channel number
 717
 718        Returns
 719        -------
 720        int
 721            the cores that are connected to the channel
 722        """
 723
 724        return self.card.get_i(SPC_DDS_CORES_ON_CH0 + channel)
 725
 726    def trg_src(self, src : int) -> None:
 727        """
 728        setup the source of where the trigger is coming from (see register `SPC_DDS_TRG_SRC` in the manual)
 729
 730        NOTE
 731        ---
 732        the trigger source is also set using the shadow register, hence only after an exec_at_trig or exec_now --
 733        
 734        Parameters
 735        ----------
 736        src : int
 737            set the trigger source:
 738            * SPCM_DDS_TRG_SRC_NONE  = 0
 739                no trigger source set, only exec_now changes what is output by the cores
 740            * SPCM_DDS_TRG_SRC_TIMER = 1
 741                an internal timer sends out triggers with a period defined by `trg_timer(period)`
 742            * SPCM_DDS_TRG_SRC_CARD  = 2
 743                use the trigger engine of the card (see the user manual for more information about setting up the trigger engine)
 744        """
 745
 746        self.set_i(SPC_DDS_TRG_SRC, src)
 747
 748    def get_trg_src(self) -> int:
 749        """
 750        get the source of where the trigger is coming from (see register `SPC_DDS_TRG_SRC` in the manual)
 751
 752        NOTE
 753        ----
 754        the trigger source is also set using the shadow register, hence only after an exec_at_trig or exec_now --
 755        
 756        Returns
 757        ----------
 758        int
 759            get one of the trigger source:
 760            * SPCM_DDS_TRG_SRC_NONE  = 0
 761                no trigger source set, only exec_now changes what is output by the cores
 762            * SPCM_DDS_TRG_SRC_TIMER = 1
 763                an internal timer sends out triggers with a period defined by `trg_timer(period)`
 764            * SPCM_DDS_TRG_SRC_CARD  = 2
 765                use the trigger engine of the card (see the user manual for more information about setting up the trigger engine)
 766        """
 767
 768        return self.card.get_i(SPC_DDS_TRG_SRC)
 769    
 770    def trg_timer(self, period : float) -> None:
 771        """
 772        set the period at which the timer should raise DDS trigger events. (see register `SPC_DDS_TRG_TIMER` in the manual)
 773
 774        NOTE
 775        ----
 776        only used in conjecture with the trigger source set to SPCM_DDS_TRG_SRC_TIMER ---
 777        
 778        Parameters
 779        ----------
 780        period : float | pint.Quantity
 781            the time between DDS trigger events in seconds
 782        """
 783
 784        period = UnitConversion.convert(period, units.s, float, rounding=None)
 785        self.set_d(SPC_DDS_TRG_TIMER, float(period))
 786    
 787    def get_trg_timer(self, return_unit = None) -> float:
 788        """
 789        get the period at which the timer should raise DDS trigger events. (see register `SPC_DDS_TRG_TIMER` in the manual)
 790
 791        NOTE
 792        ----
 793        only used in conjecture with the trigger source set to SPCM_DDS_TRG_SRC_TIMER ---
 794
 795        Parameters
 796        ----------
 797        return_unit : pint.Unit = None
 798            the unit of the returned time between DDS trigger events, by default None
 799        
 800        Returns
 801        ----------
 802        float
 803            the time between DDS trigger events in seconds
 804        """
 805
 806        return_value = self.card.get_d(SPC_DDS_TRG_TIMER)
 807        if return_unit is not None: return_value = UnitConversion.to_unit(return_value * units.s, return_unit)
 808        return return_value
 809
 810    def x_mode(self, xio : int, mode : int) -> None:
 811        """
 812        setup the kind of output that the XIO outputs will give (see register `SPC_DDS_X0_MODE` in the manual)
 813
 814        Parameters
 815        ----------
 816        xio : int
 817            the XIO channel number
 818        mode : int
 819            the mode that the channel needs to run in
 820        """
 821
 822        self.set_i(SPC_DDS_X0_MODE + xio, mode)
 823
 824    def get_x_mode(self, xio : int) -> int:
 825        """
 826        get the kind of output that the XIO outputs will give (see register `SPC_DDS_X0_MODE` in the manual)
 827
 828        Parameters
 829        ----------
 830        xio : int
 831            the XIO channel number
 832
 833        Returns
 834        -------
 835        int
 836            the mode that the channel needs to run in
 837            SPC_DDS_XIO_SEQUENCE = 0
 838                turn on and off the XIO channels using commands in the DDS cmd queue
 839            SPC_DDS_XIO_ARM = 1
 840                when the DDS firmware is waiting for a trigger to come this signal is high
 841            SPC_DDS_XIO_LATCH = 2
 842                when the DDS firmware starts executing a change this signal is high
 843        """
 844
 845        return self.card.get_i(SPC_DDS_X0_MODE + xio)
 846
 847    def freq_ramp_stepsize(self, divider : int) -> None:
 848        """
 849        number of timesteps before the frequency is changed during a frequency ramp. (see register `SPC_DDS_FREQ_RAMP_STEPSIZE` in the manual)
 850
 851        NOTES
 852        -----
 853        - this is a global setting for all cores
 854        - internally the time divider is used to calculate the amount of change per event using a given frequency slope, please set the time divider before setting the frequency slope
 855        
 856        Parameters
 857        ----------
 858        divider : int
 859            the number of DDS timesteps that a value is kept constant during a frequency ramp
 860        """
 861
 862        self.set_i(SPC_DDS_FREQ_RAMP_STEPSIZE, int(divider))
 863
 864    def get_freq_ramp_stepsize(self) -> int:
 865        """
 866        get the number of timesteps before the frequency is changed during a frequency ramp. (see register `SPC_DDS_FREQ_RAMP_STEPSIZE` in the manual)
 867        
 868        NOTES
 869        -----
 870        - this is a global setting for all cores
 871        - internally the time divider is used to calculate the amount of change per event using a given frequency slope, please set the time divider before setting the frequency slope
 872        
 873        Returns
 874        ----------
 875        divider : int
 876            the number of DDS timesteps that a value is kept constant during a frequency ramp
 877        """
 878
 879        return self.card.get_i(SPC_DDS_FREQ_RAMP_STEPSIZE)
 880
 881    def amp_ramp_stepsize(self, divider : int) -> None:
 882        """
 883        number of timesteps before the amplitude is changed during a frequency ramp. (see register `SPC_DDS_AMP_RAMP_STEPSIZE` in the manual)
 884
 885        NOTES
 886        -----
 887        - this is a global setting for all cores
 888        - internally the time divider is used to calculate the amount of change per event using a given amplitude slope, 
 889            please set the time divider before setting the amplitude slope
 890        
 891        Parameters
 892        ----------
 893        divider : int
 894            the number of DDS timesteps that a value is kept constant during an amplitude ramp
 895        """
 896
 897        self.set_i(SPC_DDS_AMP_RAMP_STEPSIZE, int(divider))
 898
 899    def get_amp_ramp_stepsize(self) -> int:
 900        """
 901        get the number of timesteps before the amplitude is changed during a frequency ramp. (see register `SPC_DDS_AMP_RAMP_STEPSIZE` in the manual)
 902
 903        NOTES
 904        -----
 905        - this is a global setting for all cores
 906        - internally the time divider is used to calculate the amount of change per event using a given amplitude slope, 
 907            please set the time divider before setting the amplitude slope
 908        
 909        Returns
 910        ----------
 911        divider : int
 912            the number of DDS timesteps that a value is kept constant during an amplitude ramp
 913        """
 914
 915        return self.card.get_i(SPC_DDS_AMP_RAMP_STEPSIZE)
 916
 917    # DDS "static" parameters
 918    # def amp(self, core_index : int, amplitude : float) -> None:
 919    def amp(self, *args) -> None:
 920        """
 921        set the amplitude of the sine wave of a specific core (see register `SPC_DDS_CORE0_AMP` in the manual)
 922        
 923        Parameters
 924        ----------
 925        core_index : int (optional)
 926            the index of the core to be changed
 927        amplitude : float
 928            the value between 0 and 1 corresponding to the amplitude
 929        """
 930
 931        if len(args) == 1:
 932            amplitude = args[0]
 933            for core in self.cores:
 934                core.amp(amplitude)
 935        elif len(args) == 2:
 936            core_index, amplitude = args
 937            self.cores[core_index].amp(amplitude)
 938        else:
 939            raise TypeError("amp() takes 1 or 2 positional arguments ({} given)".format(len(args) + 1))
 940        # self.set_d(SPC_DDS_CORE0_AMP + core_index, float(amplitude))
 941    # aliases
 942    amplitude = amp
 943
 944    def get_amp(self, core_index : int, return_unit = None) -> float:
 945        """
 946        gets the amplitude of the sine wave of a specific core (see register `SPC_DDS_CORE0_AMP` in the manual)
 947        
 948        Parameters
 949        ----------
 950        core_index : int
 951            the index of the core to be changed
 952        return_unit : pint.Unit = None
 953            the unit of the returned amplitude, by default None
 954
 955        Returns
 956        -------
 957        float | pint.Quantity
 958            the value between 0 and 1 corresponding to the amplitude of the specific core or in the specified unit
 959        """
 960
 961        return self.cores[core_index].get_amp(return_unit)
 962        # return self.card.get_d(SPC_DDS_CORE0_AMP + core_index)
 963    # aliases
 964    get_amplitude = get_amp
 965
 966    def avail_amp_min(self) -> float:
 967        """
 968        get the minimum available amplitude (see register `SPC_DDS_AVAIL_AMP_MIN` in the manual)
 969
 970        Returns
 971        -------
 972        float
 973            the minimum available amplitude
 974        
 975        TODO: unitize!
 976        """
 977
 978        return self.card.get_d(SPC_DDS_AVAIL_AMP_MIN)
 979    
 980    def avail_amp_max(self) -> float:
 981        """
 982        get the maximum available amplitude (see register `SPC_DDS_AVAIL_AMP_MAX` in the manual)
 983
 984        Returns
 985        -------
 986        float
 987            the maximum available amplitude
 988        
 989        TODO: unitize!
 990        """
 991
 992        return self.card.get_d(SPC_DDS_AVAIL_AMP_MAX)
 993    
 994    def avail_amp_step(self) -> float:
 995        """
 996        get the step size of the available amplitudes (see register `SPC_DDS_AVAIL_AMP_STEP` in the manual)
 997
 998        Returns
 999        -------
1000        float
1001            the step size of the available amplitudes
1002        
1003        TODO: unitize!
1004        """
1005
1006        return self.card.get_d(SPC_DDS_AVAIL_AMP_STEP)
1007
1008    # def freq(self, core_index : int, frequency : float) -> None:
1009    def freq(self, *args) -> None:
1010        """
1011        set the frequency of the sine wave of a specific core (see register `SPC_DDS_CORE0_FREQ` in the manual)
1012        
1013        Parameters
1014        ----------
1015        core_index : int (optional)
1016            the index of the core to be changed
1017        frequency : float
1018            the value of the frequency in Hz
1019        """
1020
1021        if len(args) == 1:
1022            frequency = args[0]
1023            for core in self.cores:
1024                core.freq(frequency)
1025        elif len(args) == 2:
1026            core_index, frequency = args
1027            self.cores[core_index].freq(frequency)
1028        else:
1029            raise TypeError("freq() takes 1 or 2 positional arguments ({} given)".format(len(args) + 1))
1030        # self.set_d(SPC_DDS_CORE0_FREQ + core_index, float(frequency))
1031    # aliases
1032    frequency = freq
1033
1034    def get_freq(self, core_index : int, return_unit = None) -> float:
1035        """
1036        gets the frequency of the sine wave of a specific core (see register `SPC_DDS_CORE0_FREQ` in the manual)
1037        
1038        Parameters
1039        ----------
1040        core_index : int
1041            the index of the core to be changed
1042        return_unit : pint.Unit = None
1043            the unit of the returned frequency, by default None
1044        
1045        Returns
1046        -------
1047        float | pint.Quantity
1048            the value of the frequency in Hz the specific core or in the specified unit
1049        """
1050
1051        return self.cores[core_index].get_freq(return_unit)
1052    # aliases
1053    get_frequency = get_freq
1054
1055    def avail_freq_min(self) -> float:
1056        """
1057        get the minimum available frequency (see register `SPC_DDS_AVAIL_FREQ_MIN` in the manual)
1058
1059        Returns
1060        -------
1061        float
1062            the minimum available frequency
1063        
1064        TODO: unitize!
1065        """
1066
1067        return self.card.get_d(SPC_DDS_AVAIL_FREQ_MIN)
1068    
1069    def avail_freq_max(self) -> float:
1070        """
1071        get the maximum available frequency (see register `SPC_DDS_AVAIL_FREQ_MAX` in the manual)
1072
1073        Returns
1074        -------
1075        float
1076            the maximum available frequency
1077        
1078        TODO: unitize!
1079        """
1080
1081        return self.card.get_d(SPC_DDS_AVAIL_FREQ_MAX)
1082    
1083    def avail_freq_step(self) -> float:
1084        """
1085        get the step size of the available frequencies (see register `SPC_DDS_AVAIL_FREQ_STEP` in the manual)
1086
1087        Returns
1088        -------
1089        float
1090            the step size of the available frequencies
1091        
1092        TODO: unitize!
1093        """
1094
1095        return self.card.get_d(SPC_DDS_AVAIL_FREQ_STEP)
1096
1097    # def phase(self, core_index : int, phase : float) -> None:
1098    def phase(self, *args) -> None:
1099        """
1100        set the phase of the sine wave of a specific core (see register `SPC_DDS_CORE0_PHASE` in the manual)
1101        
1102        Parameters
1103        ----------
1104        core_index : int (optional)
1105            the index of the core to be changed
1106        phase : float
1107            the value between 0 and 360 degrees of the phase
1108        """
1109
1110        if len(args) == 1:
1111            phase = args[0]
1112            for core in self.cores:
1113                core.phase(phase)
1114        elif len(args) == 2:
1115            core_index, phase = args
1116            self.cores[core_index].phase(phase)
1117        else:
1118            raise TypeError("phase() takes 1 or 2 positional arguments ({} given)".format(len(args) + 1))
1119        # self.set_d(SPC_DDS_CORE0_PHASE + core_index, float(phase))
1120
1121    def get_phase(self, core_index : int, return_unit = None) -> float:
1122        """
1123        gets the phase of the sine wave of a specific core (see register `SPC_DDS_CORE0_PHASE` in the manual)
1124        
1125        Parameters
1126        ----------
1127        core_index : int
1128            the index of the core to be changed
1129        return_unit : pint.Unit = None
1130            the unit of the returned phase, by default None
1131        
1132        Returns
1133        -------
1134        float
1135            the value between 0 and 360 degrees of the phase
1136        """
1137
1138        return self.cores[core_index].get_phase(return_unit)
1139      
1140    def avail_phase_min(self) -> float:
1141        """
1142        get the minimum available phase (see register `SPC_DDS_AVAIL_PHASE_MIN` in the manual)
1143
1144        Returns
1145        -------
1146        float
1147            the minimum available phase
1148        
1149        TODO: unitize!
1150        """
1151
1152        return self.card.get_d(SPC_DDS_AVAIL_PHASE_MIN)
1153    
1154    def avail_phase_max(self) -> float:
1155        """
1156        get the maximum available phase (see register `SPC_DDS_AVAIL_PHASE_MAX` in the manual)
1157
1158        Returns
1159        -------
1160        float
1161            the maximum available phase
1162        
1163        TODO: unitize!
1164        """
1165
1166        return self.card.get_d(SPC_DDS_AVAIL_PHASE_MAX)
1167    
1168    def avail_phase_step(self) -> float:
1169        """
1170        get the step size of the available phases (see register `SPC_DDS_AVAIL_PHASE_STEP` in the manual)
1171
1172        Returns
1173        -------
1174        float
1175            the step size of the available phases
1176        
1177        TODO: unitize!
1178        """
1179
1180        return self.card.get_d(SPC_DDS_AVAIL_PHASE_STEP)
1181
1182    def x_manual_output(self, state_mask : int) -> None:
1183        """
1184        set the output of the xio channels using a bit mask (see register `SPC_DDS_X_MANUAL_OUTPUT` in the manual)
1185        
1186        Parameters
1187        ----------
1188        state_mask : int
1189            bit mask where the bits correspond to specific channels and 1 to on and 0 to off.
1190        """
1191
1192        self.set_i(SPC_DDS_X_MANUAL_OUTPUT, state_mask)
1193
1194    def get_x_manual_output(self) -> int:
1195        """
1196        get the output of the xio channels using a bit mask (see register `SPC_DDS_X_MANUAL_OUTPUT` in the manual)
1197        
1198        Returns
1199        ----------
1200        int
1201            bit mask where the bits correspond to specific channels and 1 to on and 0 to off.
1202        """
1203
1204        return self.card.get_i(SPC_DDS_X_MANUAL_OUTPUT)
1205
1206    # DDS dynamic parameters
1207    # def freq_slope(self, core_index : int, slope : float) -> None:
1208    def freq_slope(self, *args) -> None:
1209        """
1210        set the frequency slope of the linearly changing frequency of the sine wave of a specific core (see register `SPC_DDS_CORE0_FREQ_SLOPE` in the manual)
1211        
1212        Parameters
1213        ----------
1214        core_index : int (optional)
1215            the index of the core to be changed
1216        slope : float
1217            the rate of frequency change in Hz/s
1218        """
1219
1220        if len(args) == 1:
1221            slope = args[0]
1222            for core in self.cores:
1223                core.freq_slope(slope)
1224        elif len(args) == 2:
1225            core_index, slope = args
1226            self.cores[core_index].freq_slope(slope)
1227        else:
1228            raise TypeError("freq_slope() takes 1 or 2 positional arguments ({} given)".format(len(args) + 1))
1229        # self.set_d(SPC_DDS_CORE0_FREQ_SLOPE + core_index, float(slope))
1230    # aliases
1231    frequency_slope = freq_slope
1232
1233    def get_freq_slope(self, core_index : int, return_unit=None) -> float:
1234        """
1235        get the frequency slope of the linearly changing frequency of the sine wave of a specific core (see register `SPC_DDS_CORE0_FREQ_SLOPE` in the manual)
1236        
1237        Parameters
1238        ----------
1239        core_index : int
1240            the index of the core to be changed
1241        return_unit : pint.Unit = None
1242            the unit of the returned frequency slope, by default None
1243        
1244        Returns
1245        -------
1246        float
1247            the rate of frequency change in Hz/s
1248        """
1249
1250        return self.cores[core_index].get_freq_slope(return_unit)
1251    # aliases
1252    get_frequency_slope = get_freq_slope
1253
1254    def avail_freq_slope_min(self) -> float:
1255        """
1256        get the minimum available frequency slope (see register `SPC_DDS_AVAIL_FREQ_SLOPE_MIN` in the manual)
1257
1258        Returns
1259        -------
1260        float
1261            the minimum available frequency slope
1262        
1263        TODO: unitize!
1264        """
1265
1266        return self.card.get_d(SPC_DDS_AVAIL_FREQ_SLOPE_MIN)
1267    
1268    def avail_freq_slope_max(self) -> float:
1269        """
1270        get the maximum available frequency slope (see register `SPC_DDS_AVAIL_FREQ_SLOPE_MAX` in the manual)
1271
1272        Returns
1273        -------
1274        float
1275            the maximum available frequency slope
1276        
1277        TODO: unitize!
1278        """
1279
1280        return self.card.get_d(SPC_DDS_AVAIL_FREQ_SLOPE_MAX)
1281    
1282    def avail_freq_slope_step(self) -> float:
1283        """
1284        get the step size of the available frequency slopes (see register `SPC_DDS_AVAIL_FREQ_SLOPE_STEP` in the manual)
1285
1286        Returns
1287        -------
1288        float
1289            the step size of the available frequency slopes
1290        
1291        TODO: unitize!
1292        """
1293
1294        return self.card.get_d(SPC_DDS_AVAIL_FREQ_SLOPE_STEP)
1295
1296    # def amp_slope(self, core_index : int, slope : float) -> None:
1297    def amp_slope(self, *args) -> None:
1298        """
1299        set the amplitude slope of the linearly changing amplitude of the sine wave of a specific core (see register `SPC_DDS_CORE0_AMP_SLOPE` in the manual)
1300        
1301        Parameters
1302        ----------
1303        core_index : int (optional)
1304            the index of the core to be changed
1305        slope : float
1306            the rate of amplitude change in 1/s
1307        """
1308
1309        if len(args) == 1:
1310            slope = args[0]
1311            for core in self.cores:
1312                core.amp_slope(slope)
1313        elif len(args) == 2:
1314            core_index, slope = args
1315            self.cores[core_index].amp_slope(slope)
1316        else:
1317            raise TypeError("amp_slope() takes 1 or 2 positional arguments ({} given)".format(len(args) + 1))
1318        # self.set_d(SPC_DDS_CORE0_AMP_SLOPE + core_index, float(slope))
1319    # aliases
1320    amplitude_slope = amp_slope
1321
1322    def get_amp_slope(self, core_index : int, return_unit = None) -> float:
1323        """
1324        set the amplitude slope of the linearly changing amplitude of the sine wave of a specific core (see register `SPC_DDS_CORE0_AMP_SLOPE` in the manual)
1325        
1326        Parameters
1327        ----------
1328        core_index : int
1329            the index of the core to be changed
1330        return_unit : pint.Unit = None
1331            the unit of the returned amplitude slope, by default None
1332        
1333        Returns
1334        -------
1335        float
1336            the rate of amplitude change in 1/s
1337        """
1338
1339        return self.cores[core_index].get_amp_slope(return_unit)
1340    # aliases
1341    amplitude_slope = amp_slope
1342
1343    def avail_amp_slope_min(self) -> float:
1344        """
1345        get the minimum available amplitude slope (see register `SPC_DDS_AVAIL_AMP_SLOPE_MIN` in the manual)
1346
1347        Returns
1348        -------
1349        float
1350            the minimum available amplitude slope
1351        
1352        TODO: unitize!
1353        """
1354
1355        return self.card.get_d(SPC_DDS_AVAIL_AMP_SLOPE_MIN)
1356    
1357    def avail_amp_slope_max(self) -> float:
1358        """
1359        get the maximum available amplitude slope (see register `SPC_DDS_AVAIL_AMP_SLOPE_MAX` in the manual)
1360
1361        Returns
1362        -------
1363        float
1364            the maximum available amplitude slope
1365        
1366        TODO: unitize!
1367        """
1368
1369        return self.card.get_d(SPC_DDS_AVAIL_AMP_SLOPE_MAX)
1370    
1371    def avail_amp_slope_step(self) -> float:
1372        """
1373        get the step size of the available amplitude slopes (see register `SPC_DDS_AVAIL_AMP_SLOPE_STEP` in the manual)
1374
1375        Returns
1376        -------
1377        float
1378            the step size of the available amplitude slopes
1379        
1380        TODO: unitize!
1381        """
1382
1383        return self.card.get_d(SPC_DDS_AVAIL_AMP_SLOPE_STEP)
1384
1385    # DDS control
1386    def cmd(self, command : int) -> None:
1387        """
1388        execute a DDS specific control flow command (see register `SPC_DDS_CMD` in the manual)
1389        
1390        Parameters
1391        ----------
1392        command : int
1393            DDS specific command
1394        """
1395
1396        self.set_i(SPC_DDS_CMD, command)
1397
1398    def exec_at_trg(self) -> None:
1399        """
1400        execute the commands in the shadow register at the next trigger event (see register `SPC_DDS_CMD` in the manual)
1401        """
1402        self.cmd(SPCM_DDS_CMD_EXEC_AT_TRG)
1403    # aliases
1404    arm = exec_at_trg
1405    wait_for_trg = exec_at_trg
1406    
1407    def exec_now(self) -> None:
1408        """
1409        execute the commands in the shadow register as soon as possible (see register `SPC_DDS_CMD` in the manual)
1410        """
1411
1412        self.cmd(SPCM_DDS_CMD_EXEC_NOW)
1413    # aliases
1414    direct_latch = exec_now
1415
1416    def trg_count(self) -> int:
1417        """
1418        get the number of trigger exec_at_trg and exec_now command that have been executed (see register `SPC_DDS_TRG_COUNT` in the manual)
1419
1420        Returns
1421        -------
1422        int
1423            the number of trigger exec_at_trg and exec_now command that have been executed
1424        """
1425
1426        return self.card.get_i(SPC_DDS_TRG_COUNT)
1427    
1428    def write_to_card(self, flags=0) -> None:
1429        """
1430        send a list of all the commands that came after the last write_list and send them to the card (see register `SPC_DDS_CMD` in the manual)
1431
1432        Parameters
1433        ----------
1434        flags : int = 0
1435            the flags that can be set with the write_to_card command
1436        """
1437        
1438        self.cmd(SPCM_DDS_CMD_WRITE_TO_CARD | flags)
1439    
1440    # DDS helper functions
1441    def kwargs2mask(self, kwargs : dict[str, bool], prefix : str = "") -> int:
1442        """
1443        DDS helper: transform a dictionary with keys with a specific prefix to a bitmask
1444
1445        Parameters
1446        ----------
1447        kwargs : dict
1448            dictonary with keys with a specific prefix and values given by bools
1449        prefix : str
1450            a prefix for the key names
1451        
1452        Returns
1453        -------
1454        int
1455            bit mask
1456        
1457        Example
1458        -------
1459        ['core_0' = True, 'core_2' = False, 'core_3' = True] => 0b1001 = 9
1460        """
1461        
1462        mask = 0
1463        for keyword, value in kwargs.items():
1464            bit = int(keyword[len(prefix):])
1465            if value:
1466                mask |= 1 << bit
1467            else:
1468                mask &= ~(1 << bit)
1469        return mask
1470    # aliases
1471    k2m = kwargs2mask

a higher-level abstraction of the SpcmCardFunctionality class to implement DDS functionality

The DDS firmware allows the user a certain maximum number of dds cores, that each on it's own generates a sine wave with the following parameters:

  • static parameters:
    • frequency
    • amplitude
    • phase
  • dynamic parameters:
    • frequency_slope changes the active frequency of the dds core with a linear slope
    • amplitude_slope changes the active amplitude of the dds core with a linear slope Each of these cores can either be added together and outputted, or specific groups of cores can be added together and outputted on a specific hardware output channel. Furthermore, specific dds cores can be connected to input parameters of another dds core.

For more information about what setups are available, please have a look at the user manual for your specific card.

Commands

The DDS functionality is controlled through commands that are listed and then written to the card. These written lists of commands are collected in a shadow register and are transferred to the active register when a trigger is received.

There are three different trigger sources, that can be set with the method 'trg_source()':

  • SPCM_DDS_TRG_SRC_NONE = 0 no triggers are generated and the commands are only transfered to the active register when a exec_now command is send
  • SPCM_DDS_TRG_SRC_TIMER = 1 the triggers are generated on a timed grid with a period that can be set by the method 'trg_timer()'
  • SPCM_DDS_TRG_SRC_CARD = 2 the triggers come from the card internal trigger logic (for more information, see our product's user manual on how to setup the different triggers). In the DDS-mode multiple triggers can be processed, as with the mode SPC_STD_REP_SINGLERESTART.
Note

also the trigger source setting happens when a trigger comes. Hence a change of the trigger mode only happens after an 'arm()' command was send and an internal trigger was received.

DDS(*args, **kwargs)
459    def __init__(self, *args, **kwargs) -> None:
460        super().__init__(*args, **kwargs)
461        self.channels = kwargs.get("channels", None)
462        self.check_features = kwargs.get("check_features", False)
463        self.no_units = kwargs.get("no_units", False)
464        self.cores = []
465
466        if self.no_units:
467            self.core_type = DDSBareCore
468        else:
469            self.core_type = DDSCore
470
471        self.load_cores()
472        # Check if DDS feature is installed
473        if self.check_features:
474            features = self.card.ext_features()
475            if not ((features & SPCM_FEAT_EXTFW_DDS20) or (features & SPCM_FEAT_EXTFW_DDS50)):
476                raise SpcmException("The DDS feature is not installed on the card")

Takes a Card object that is used by the functionality

Parameters
  • card (Card): a Card object on which the functionality works
cores: list[DDSCore] = []
channels: Channels = None
check_features: bool = False
no_units: bool = False
core_type = <class 'DDSCore'>
def load_cores(self):
478    def load_cores(self):
479        """
480        load the cores of the DDS functionality
481        """
482
483        self.cores = []
484        num_cores = self.num_cores()
485        
486        if self.channels is not None:
487            for channel in self.channels:
488                cores_on_channel = self.get_cores_on_channel(channel.index)
489                for core in range(num_cores):
490                    if cores_on_channel & (1 << core):
491                        self._channel_from_core[core] = channel
492        
493        for core in range(num_cores):
494            if core in self._channel_from_core:
495                self.cores.append(self.core_type(core, self, channel=self._channel_from_core[core]))
496            else:
497                self.cores.append(self.core_type(core, self))

load the cores of the DDS functionality

def set_i(self, reg: int, value: int) -> None:
554    def set_i(self, reg : int, value : int) -> None:
555        """
556        set an integer value to a register
557
558        Parameters
559        ----------
560        reg : int
561            the register to be changed
562        value : int
563            the value to be set
564        
565        Raises
566        ------
567        SpcmException
568            if the command list is full
569        """
570
571        self.card.set_i(reg, value)

set an integer value to a register

Parameters
  • reg (int): the register to be changed
  • value (int): the value to be set
Raises
  • SpcmException: if the command list is full
def set_d(self, reg: int, value: float) -> None:
573    def set_d(self, reg : int, value : float) -> None:
574        """
575        set a double value to a register
576
577        Parameters
578        ----------
579        reg : int
580            the register to be changed
581        value : float
582            the value to be set
583        """
584    
585        self.card.set_d(reg, value)

set a double value to a register

Parameters
  • reg (int): the register to be changed
  • value (float): the value to be set
def reset(self) -> None:
587    def reset(self) -> None:
588        """
589        Resets the DDS specific part of the firmware (see register `SPC_DDS_CMD` in the manual)
590        """
591
592        self.cmd(SPCM_DDS_CMD_RESET)

Resets the DDS specific part of the firmware (see register SPC_DDS_CMD in the manual)

def num_cores(self) -> int:
595    def num_cores(self) -> int:
596        """
597        get the total num of available cores on the card. (see register `SPC_DDS_NUM_CORES` in the manual)
598
599        Returns
600        -------
601        int
602            the available number of dds cores
603        """
604        return self.card.get_i(SPC_DDS_NUM_CORES)

get the total num of available cores on the card. (see register SPC_DDS_NUM_CORES in the manual)

Returns
  • int: the available number of dds cores
def queue_cmd_max(self):
606    def queue_cmd_max(self):
607        """
608        get the total number of commands that can be hold by the queue. (see register `SPC_DDS_QUEUE_CMD_MAX` in the manual)
609
610        Returns
611        -------
612        int
613            the total number of commands
614        """
615        return self.card.get_i(SPC_DDS_QUEUE_CMD_MAX)

get the total number of commands that can be hold by the queue. (see register SPC_DDS_QUEUE_CMD_MAX in the manual)

Returns
  • int: the total number of commands
def queue_cmd_count(self):
617    def queue_cmd_count(self):
618        """
619        get the current number of commands that are in the queue. (see register `SPC_DDS_QUEUE_CMD_COUNT` in the manual)
620
621        Returns
622        -------
623        int
624            the current number of commands
625        """
626        return self.card.get_i(SPC_DDS_QUEUE_CMD_COUNT)

get the current number of commands that are in the queue. (see register SPC_DDS_QUEUE_CMD_COUNT in the manual)

Returns
  • int: the current number of commands
def status(self):
628    def status(self):
629        return self.card.get_i(SPC_DDS_STATUS)
def data_transfer_mode(self, mode: int) -> None:
632    def data_transfer_mode(self, mode : int) -> None:
633        """
634        set the data transfer mode for the DDS functionality (see register `SPC_DDS_DATA_TRANSFER_MODE` in the manual)
635
636        Parameters
637        ----------
638        mode : int
639            the data transfer mode:
640            * SPCM_DDS_DTM_SINGLE = 0
641                the data is transferred using single commands (with lower latency)
642            * SPCM_DDS_DTM_DMA = 1
643                the data is transferred using DMA (with higher bandwidth)
644        """
645
646        self._dtm = mode
647        self.set_i(SPC_DDS_DATA_TRANSFER_MODE, mode)

set the data transfer mode for the DDS functionality (see register SPC_DDS_DATA_TRANSFER_MODE in the manual)

Parameters
  • mode (int): the data transfer mode:
    • SPCM_DDS_DTM_SINGLE = 0 the data is transferred using single commands (with lower latency)
    • SPCM_DDS_DTM_DMA = 1 the data is transferred using DMA (with higher bandwidth)
def get_data_transfer_mode(self) -> int:
649    def get_data_transfer_mode(self) -> int:
650        """
651        get the data transfer mode for the DDS functionality (see register `SPC_DDS_DATA_TRANSFER_MODE` in the manual)
652
653        Returns
654        -------
655        int
656            the data transfer mode:
657            * SPCM_DDS_DTM_SINGLE = 0
658                the data is transferred using single commands (with lower latency)
659            * SPCM_DDS_DTM_DMA = 1
660                the data is transferred using DMA (with higher bandwidth)
661        """
662
663        self._dtm = self.card.get_i(SPC_DDS_DATA_TRANSFER_MODE)
664        return self._dtm

get the data transfer mode for the DDS functionality (see register SPC_DDS_DATA_TRANSFER_MODE in the manual)

Returns
  • int: the data transfer mode:
    • SPCM_DDS_DTM_SINGLE = 0 the data is transferred using single commands (with lower latency)
    • SPCM_DDS_DTM_DMA = 1 the data is transferred using DMA (with higher bandwidth)
def phase_behaviour(self, behaviour: int) -> None:
666    def phase_behaviour(self, behaviour : int) -> None:
667        """
668        set the phase behaviour of the DDS cores (see register `SPC_DDS_PHASE_BEHAVIOUR` in the manual)
669
670        Parameters
671        ----------
672        behaviour : int
673            the phase behaviour
674        """
675
676        self.set_i(SPC_DDS_PHASE_BEHAVIOUR, behaviour)

set the phase behaviour of the DDS cores (see register SPC_DDS_PHASE_BEHAVIOUR in the manual)

Parameters
  • behaviour (int): the phase behaviour
def get_phase_behaviour(self) -> int:
678    def get_phase_behaviour(self) -> int:
679        """
680        get the phase behaviour of the DDS cores (see register `SPC_DDS_PHASE_BEHAVIOUR` in the manual)
681
682        Returns
683        -------
684        int
685            the phase behaviour
686        """
687
688        return self.card.get_i(SPC_DDS_PHASE_BEHAVIOUR)

get the phase behaviour of the DDS cores (see register SPC_DDS_PHASE_BEHAVIOUR in the manual)

Returns
  • int: the phase behaviour
def cores_on_channel(self, channel: int, *args) -> None:
690    def cores_on_channel(self, channel : int, *args) -> None:
691        """
692        setup the cores that are connected to a specific channel (see register `SPC_DDS_CORES_ON_CH0` in the manual)
693
694        Parameters
695        ----------
696        channel : int
697            the channel number
698        *args : int
699            the cores that are connected to the channel
700        
701        TODO: change the channel associated with each core
702        """
703
704        mask = 0
705        for core in args:
706            mask |= core
707        self.set_i(SPC_DDS_CORES_ON_CH0 + channel, mask)

setup the cores that are connected to a specific channel (see register SPC_DDS_CORES_ON_CH0 in the manual)

Parameters
  • channel (int): the channel number
  • *args (int): the cores that are connected to the channel
  • TODO (change the channel associated with each core):
def get_cores_on_channel(self, channel: int) -> int:
709    def get_cores_on_channel(self, channel : int) -> int:
710        """
711        get the cores that are connected to a specific channel (see register `SPC_DDS_CORES_ON_CH0` in the manual)
712
713        Parameters
714        ----------
715        channel : int
716            the channel number
717
718        Returns
719        -------
720        int
721            the cores that are connected to the channel
722        """
723
724        return self.card.get_i(SPC_DDS_CORES_ON_CH0 + channel)

get the cores that are connected to a specific channel (see register SPC_DDS_CORES_ON_CH0 in the manual)

Parameters
  • channel (int): the channel number
Returns
  • int: the cores that are connected to the channel
def trg_src(self, src: int) -> None:
726    def trg_src(self, src : int) -> None:
727        """
728        setup the source of where the trigger is coming from (see register `SPC_DDS_TRG_SRC` in the manual)
729
730        NOTE
731        ---
732        the trigger source is also set using the shadow register, hence only after an exec_at_trig or exec_now --
733        
734        Parameters
735        ----------
736        src : int
737            set the trigger source:
738            * SPCM_DDS_TRG_SRC_NONE  = 0
739                no trigger source set, only exec_now changes what is output by the cores
740            * SPCM_DDS_TRG_SRC_TIMER = 1
741                an internal timer sends out triggers with a period defined by `trg_timer(period)`
742            * SPCM_DDS_TRG_SRC_CARD  = 2
743                use the trigger engine of the card (see the user manual for more information about setting up the trigger engine)
744        """
745
746        self.set_i(SPC_DDS_TRG_SRC, src)

setup the source of where the trigger is coming from (see register SPC_DDS_TRG_SRC in the manual)

NOTE

the trigger source is also set using the shadow register, hence only after an exec_at_trig or exec_now --

Parameters
  • src (int): set the trigger source:
    • SPCM_DDS_TRG_SRC_NONE = 0 no trigger source set, only exec_now changes what is output by the cores
    • SPCM_DDS_TRG_SRC_TIMER = 1 an internal timer sends out triggers with a period defined by trg_timer(period)
    • SPCM_DDS_TRG_SRC_CARD = 2 use the trigger engine of the card (see the user manual for more information about setting up the trigger engine)
def get_trg_src(self) -> int:
748    def get_trg_src(self) -> int:
749        """
750        get the source of where the trigger is coming from (see register `SPC_DDS_TRG_SRC` in the manual)
751
752        NOTE
753        ----
754        the trigger source is also set using the shadow register, hence only after an exec_at_trig or exec_now --
755        
756        Returns
757        ----------
758        int
759            get one of the trigger source:
760            * SPCM_DDS_TRG_SRC_NONE  = 0
761                no trigger source set, only exec_now changes what is output by the cores
762            * SPCM_DDS_TRG_SRC_TIMER = 1
763                an internal timer sends out triggers with a period defined by `trg_timer(period)`
764            * SPCM_DDS_TRG_SRC_CARD  = 2
765                use the trigger engine of the card (see the user manual for more information about setting up the trigger engine)
766        """
767
768        return self.card.get_i(SPC_DDS_TRG_SRC)

get the source of where the trigger is coming from (see register SPC_DDS_TRG_SRC in the manual)

NOTE

the trigger source is also set using the shadow register, hence only after an exec_at_trig or exec_now --

Returns
  • int: get one of the trigger source:
    • SPCM_DDS_TRG_SRC_NONE = 0 no trigger source set, only exec_now changes what is output by the cores
    • SPCM_DDS_TRG_SRC_TIMER = 1 an internal timer sends out triggers with a period defined by trg_timer(period)
    • SPCM_DDS_TRG_SRC_CARD = 2 use the trigger engine of the card (see the user manual for more information about setting up the trigger engine)
def trg_timer(self, period: float) -> None:
770    def trg_timer(self, period : float) -> None:
771        """
772        set the period at which the timer should raise DDS trigger events. (see register `SPC_DDS_TRG_TIMER` in the manual)
773
774        NOTE
775        ----
776        only used in conjecture with the trigger source set to SPCM_DDS_TRG_SRC_TIMER ---
777        
778        Parameters
779        ----------
780        period : float | pint.Quantity
781            the time between DDS trigger events in seconds
782        """
783
784        period = UnitConversion.convert(period, units.s, float, rounding=None)
785        self.set_d(SPC_DDS_TRG_TIMER, float(period))

set the period at which the timer should raise DDS trigger events. (see register SPC_DDS_TRG_TIMER in the manual)

NOTE

only used in conjecture with the trigger source set to SPCM_DDS_TRG_SRC_TIMER ---

Parameters
  • period (float | pint.Quantity): the time between DDS trigger events in seconds
def get_trg_timer(self, return_unit=None) -> float:
787    def get_trg_timer(self, return_unit = None) -> float:
788        """
789        get the period at which the timer should raise DDS trigger events. (see register `SPC_DDS_TRG_TIMER` in the manual)
790
791        NOTE
792        ----
793        only used in conjecture with the trigger source set to SPCM_DDS_TRG_SRC_TIMER ---
794
795        Parameters
796        ----------
797        return_unit : pint.Unit = None
798            the unit of the returned time between DDS trigger events, by default None
799        
800        Returns
801        ----------
802        float
803            the time between DDS trigger events in seconds
804        """
805
806        return_value = self.card.get_d(SPC_DDS_TRG_TIMER)
807        if return_unit is not None: return_value = UnitConversion.to_unit(return_value * units.s, return_unit)
808        return return_value

get the period at which the timer should raise DDS trigger events. (see register SPC_DDS_TRG_TIMER in the manual)

NOTE

only used in conjecture with the trigger source set to SPCM_DDS_TRG_SRC_TIMER ---

Parameters
  • return_unit (pint.Unit = None): the unit of the returned time between DDS trigger events, by default None
Returns
  • float: the time between DDS trigger events in seconds
def x_mode(self, xio: int, mode: int) -> None:
810    def x_mode(self, xio : int, mode : int) -> None:
811        """
812        setup the kind of output that the XIO outputs will give (see register `SPC_DDS_X0_MODE` in the manual)
813
814        Parameters
815        ----------
816        xio : int
817            the XIO channel number
818        mode : int
819            the mode that the channel needs to run in
820        """
821
822        self.set_i(SPC_DDS_X0_MODE + xio, mode)

setup the kind of output that the XIO outputs will give (see register SPC_DDS_X0_MODE in the manual)

Parameters
  • xio (int): the XIO channel number
  • mode (int): the mode that the channel needs to run in
def get_x_mode(self, xio: int) -> int:
824    def get_x_mode(self, xio : int) -> int:
825        """
826        get the kind of output that the XIO outputs will give (see register `SPC_DDS_X0_MODE` in the manual)
827
828        Parameters
829        ----------
830        xio : int
831            the XIO channel number
832
833        Returns
834        -------
835        int
836            the mode that the channel needs to run in
837            SPC_DDS_XIO_SEQUENCE = 0
838                turn on and off the XIO channels using commands in the DDS cmd queue
839            SPC_DDS_XIO_ARM = 1
840                when the DDS firmware is waiting for a trigger to come this signal is high
841            SPC_DDS_XIO_LATCH = 2
842                when the DDS firmware starts executing a change this signal is high
843        """
844
845        return self.card.get_i(SPC_DDS_X0_MODE + xio)

get the kind of output that the XIO outputs will give (see register SPC_DDS_X0_MODE in the manual)

Parameters
  • xio (int): the XIO channel number
Returns
  • int: the mode that the channel needs to run in SPC_DDS_XIO_SEQUENCE = 0 turn on and off the XIO channels using commands in the DDS cmd queue SPC_DDS_XIO_ARM = 1 when the DDS firmware is waiting for a trigger to come this signal is high SPC_DDS_XIO_LATCH = 2 when the DDS firmware starts executing a change this signal is high
def freq_ramp_stepsize(self, divider: int) -> None:
847    def freq_ramp_stepsize(self, divider : int) -> None:
848        """
849        number of timesteps before the frequency is changed during a frequency ramp. (see register `SPC_DDS_FREQ_RAMP_STEPSIZE` in the manual)
850
851        NOTES
852        -----
853        - this is a global setting for all cores
854        - internally the time divider is used to calculate the amount of change per event using a given frequency slope, please set the time divider before setting the frequency slope
855        
856        Parameters
857        ----------
858        divider : int
859            the number of DDS timesteps that a value is kept constant during a frequency ramp
860        """
861
862        self.set_i(SPC_DDS_FREQ_RAMP_STEPSIZE, int(divider))

number of timesteps before the frequency is changed during a frequency ramp. (see register SPC_DDS_FREQ_RAMP_STEPSIZE in the manual)

NOTES
  • this is a global setting for all cores
  • internally the time divider is used to calculate the amount of change per event using a given frequency slope, please set the time divider before setting the frequency slope
Parameters
  • divider (int): the number of DDS timesteps that a value is kept constant during a frequency ramp
def get_freq_ramp_stepsize(self) -> int:
864    def get_freq_ramp_stepsize(self) -> int:
865        """
866        get the number of timesteps before the frequency is changed during a frequency ramp. (see register `SPC_DDS_FREQ_RAMP_STEPSIZE` in the manual)
867        
868        NOTES
869        -----
870        - this is a global setting for all cores
871        - internally the time divider is used to calculate the amount of change per event using a given frequency slope, please set the time divider before setting the frequency slope
872        
873        Returns
874        ----------
875        divider : int
876            the number of DDS timesteps that a value is kept constant during a frequency ramp
877        """
878
879        return self.card.get_i(SPC_DDS_FREQ_RAMP_STEPSIZE)

get the number of timesteps before the frequency is changed during a frequency ramp. (see register SPC_DDS_FREQ_RAMP_STEPSIZE in the manual)

NOTES
  • this is a global setting for all cores
  • internally the time divider is used to calculate the amount of change per event using a given frequency slope, please set the time divider before setting the frequency slope
Returns
  • divider (int): the number of DDS timesteps that a value is kept constant during a frequency ramp
def amp_ramp_stepsize(self, divider: int) -> None:
881    def amp_ramp_stepsize(self, divider : int) -> None:
882        """
883        number of timesteps before the amplitude is changed during a frequency ramp. (see register `SPC_DDS_AMP_RAMP_STEPSIZE` in the manual)
884
885        NOTES
886        -----
887        - this is a global setting for all cores
888        - internally the time divider is used to calculate the amount of change per event using a given amplitude slope, 
889            please set the time divider before setting the amplitude slope
890        
891        Parameters
892        ----------
893        divider : int
894            the number of DDS timesteps that a value is kept constant during an amplitude ramp
895        """
896
897        self.set_i(SPC_DDS_AMP_RAMP_STEPSIZE, int(divider))

number of timesteps before the amplitude is changed during a frequency ramp. (see register SPC_DDS_AMP_RAMP_STEPSIZE in the manual)

NOTES
  • this is a global setting for all cores
  • internally the time divider is used to calculate the amount of change per event using a given amplitude slope, please set the time divider before setting the amplitude slope
Parameters
  • divider (int): the number of DDS timesteps that a value is kept constant during an amplitude ramp
def get_amp_ramp_stepsize(self) -> int:
899    def get_amp_ramp_stepsize(self) -> int:
900        """
901        get the number of timesteps before the amplitude is changed during a frequency ramp. (see register `SPC_DDS_AMP_RAMP_STEPSIZE` in the manual)
902
903        NOTES
904        -----
905        - this is a global setting for all cores
906        - internally the time divider is used to calculate the amount of change per event using a given amplitude slope, 
907            please set the time divider before setting the amplitude slope
908        
909        Returns
910        ----------
911        divider : int
912            the number of DDS timesteps that a value is kept constant during an amplitude ramp
913        """
914
915        return self.card.get_i(SPC_DDS_AMP_RAMP_STEPSIZE)

get the number of timesteps before the amplitude is changed during a frequency ramp. (see register SPC_DDS_AMP_RAMP_STEPSIZE in the manual)

NOTES
  • this is a global setting for all cores
  • internally the time divider is used to calculate the amount of change per event using a given amplitude slope, please set the time divider before setting the amplitude slope
Returns
  • divider (int): the number of DDS timesteps that a value is kept constant during an amplitude ramp
def amp(self, *args) -> None:
919    def amp(self, *args) -> None:
920        """
921        set the amplitude of the sine wave of a specific core (see register `SPC_DDS_CORE0_AMP` in the manual)
922        
923        Parameters
924        ----------
925        core_index : int (optional)
926            the index of the core to be changed
927        amplitude : float
928            the value between 0 and 1 corresponding to the amplitude
929        """
930
931        if len(args) == 1:
932            amplitude = args[0]
933            for core in self.cores:
934                core.amp(amplitude)
935        elif len(args) == 2:
936            core_index, amplitude = args
937            self.cores[core_index].amp(amplitude)
938        else:
939            raise TypeError("amp() takes 1 or 2 positional arguments ({} given)".format(len(args) + 1))
940        # self.set_d(SPC_DDS_CORE0_AMP + core_index, float(amplitude))

set the amplitude of the sine wave of a specific core (see register SPC_DDS_CORE0_AMP in the manual)

Parameters
  • core_index (int (optional)): the index of the core to be changed
  • amplitude (float): the value between 0 and 1 corresponding to the amplitude
def amplitude(self, *args) -> None:
919    def amp(self, *args) -> None:
920        """
921        set the amplitude of the sine wave of a specific core (see register `SPC_DDS_CORE0_AMP` in the manual)
922        
923        Parameters
924        ----------
925        core_index : int (optional)
926            the index of the core to be changed
927        amplitude : float
928            the value between 0 and 1 corresponding to the amplitude
929        """
930
931        if len(args) == 1:
932            amplitude = args[0]
933            for core in self.cores:
934                core.amp(amplitude)
935        elif len(args) == 2:
936            core_index, amplitude = args
937            self.cores[core_index].amp(amplitude)
938        else:
939            raise TypeError("amp() takes 1 or 2 positional arguments ({} given)".format(len(args) + 1))
940        # self.set_d(SPC_DDS_CORE0_AMP + core_index, float(amplitude))

set the amplitude of the sine wave of a specific core (see register SPC_DDS_CORE0_AMP in the manual)

Parameters
  • core_index (int (optional)): the index of the core to be changed
  • amplitude (float): the value between 0 and 1 corresponding to the amplitude
def get_amp(self, core_index: int, return_unit=None) -> float:
944    def get_amp(self, core_index : int, return_unit = None) -> float:
945        """
946        gets the amplitude of the sine wave of a specific core (see register `SPC_DDS_CORE0_AMP` in the manual)
947        
948        Parameters
949        ----------
950        core_index : int
951            the index of the core to be changed
952        return_unit : pint.Unit = None
953            the unit of the returned amplitude, by default None
954
955        Returns
956        -------
957        float | pint.Quantity
958            the value between 0 and 1 corresponding to the amplitude of the specific core or in the specified unit
959        """
960
961        return self.cores[core_index].get_amp(return_unit)
962        # return self.card.get_d(SPC_DDS_CORE0_AMP + core_index)

gets the amplitude of the sine wave of a specific core (see register SPC_DDS_CORE0_AMP in the manual)

Parameters
  • core_index (int): the index of the core to be changed
  • return_unit (pint.Unit = None): the unit of the returned amplitude, by default None
Returns
  • float | pint.Quantity: the value between 0 and 1 corresponding to the amplitude of the specific core or in the specified unit
def get_amplitude(self, core_index: int, return_unit=None) -> float:
944    def get_amp(self, core_index : int, return_unit = None) -> float:
945        """
946        gets the amplitude of the sine wave of a specific core (see register `SPC_DDS_CORE0_AMP` in the manual)
947        
948        Parameters
949        ----------
950        core_index : int
951            the index of the core to be changed
952        return_unit : pint.Unit = None
953            the unit of the returned amplitude, by default None
954
955        Returns
956        -------
957        float | pint.Quantity
958            the value between 0 and 1 corresponding to the amplitude of the specific core or in the specified unit
959        """
960
961        return self.cores[core_index].get_amp(return_unit)
962        # return self.card.get_d(SPC_DDS_CORE0_AMP + core_index)

gets the amplitude of the sine wave of a specific core (see register SPC_DDS_CORE0_AMP in the manual)

Parameters
  • core_index (int): the index of the core to be changed
  • return_unit (pint.Unit = None): the unit of the returned amplitude, by default None
Returns
  • float | pint.Quantity: the value between 0 and 1 corresponding to the amplitude of the specific core or in the specified unit
def avail_amp_min(self) -> float:
966    def avail_amp_min(self) -> float:
967        """
968        get the minimum available amplitude (see register `SPC_DDS_AVAIL_AMP_MIN` in the manual)
969
970        Returns
971        -------
972        float
973            the minimum available amplitude
974        
975        TODO: unitize!
976        """
977
978        return self.card.get_d(SPC_DDS_AVAIL_AMP_MIN)

get the minimum available amplitude (see register SPC_DDS_AVAIL_AMP_MIN in the manual)

Returns
  • float: the minimum available amplitude
  • TODO (unitize!):
def avail_amp_max(self) -> float:
980    def avail_amp_max(self) -> float:
981        """
982        get the maximum available amplitude (see register `SPC_DDS_AVAIL_AMP_MAX` in the manual)
983
984        Returns
985        -------
986        float
987            the maximum available amplitude
988        
989        TODO: unitize!
990        """
991
992        return self.card.get_d(SPC_DDS_AVAIL_AMP_MAX)

get the maximum available amplitude (see register SPC_DDS_AVAIL_AMP_MAX in the manual)

Returns
  • float: the maximum available amplitude
  • TODO (unitize!):
def avail_amp_step(self) -> float:
 994    def avail_amp_step(self) -> float:
 995        """
 996        get the step size of the available amplitudes (see register `SPC_DDS_AVAIL_AMP_STEP` in the manual)
 997
 998        Returns
 999        -------
1000        float
1001            the step size of the available amplitudes
1002        
1003        TODO: unitize!
1004        """
1005
1006        return self.card.get_d(SPC_DDS_AVAIL_AMP_STEP)

get the step size of the available amplitudes (see register SPC_DDS_AVAIL_AMP_STEP in the manual)

Returns
  • float: the step size of the available amplitudes
  • TODO (unitize!):
def freq(self, *args) -> None:
1009    def freq(self, *args) -> None:
1010        """
1011        set the frequency of the sine wave of a specific core (see register `SPC_DDS_CORE0_FREQ` in the manual)
1012        
1013        Parameters
1014        ----------
1015        core_index : int (optional)
1016            the index of the core to be changed
1017        frequency : float
1018            the value of the frequency in Hz
1019        """
1020
1021        if len(args) == 1:
1022            frequency = args[0]
1023            for core in self.cores:
1024                core.freq(frequency)
1025        elif len(args) == 2:
1026            core_index, frequency = args
1027            self.cores[core_index].freq(frequency)
1028        else:
1029            raise TypeError("freq() takes 1 or 2 positional arguments ({} given)".format(len(args) + 1))
1030        # self.set_d(SPC_DDS_CORE0_FREQ + core_index, float(frequency))

set the frequency of the sine wave of a specific core (see register SPC_DDS_CORE0_FREQ in the manual)

Parameters
  • core_index (int (optional)): the index of the core to be changed
  • frequency (float): the value of the frequency in Hz
def frequency(self, *args) -> None:
1009    def freq(self, *args) -> None:
1010        """
1011        set the frequency of the sine wave of a specific core (see register `SPC_DDS_CORE0_FREQ` in the manual)
1012        
1013        Parameters
1014        ----------
1015        core_index : int (optional)
1016            the index of the core to be changed
1017        frequency : float
1018            the value of the frequency in Hz
1019        """
1020
1021        if len(args) == 1:
1022            frequency = args[0]
1023            for core in self.cores:
1024                core.freq(frequency)
1025        elif len(args) == 2:
1026            core_index, frequency = args
1027            self.cores[core_index].freq(frequency)
1028        else:
1029            raise TypeError("freq() takes 1 or 2 positional arguments ({} given)".format(len(args) + 1))
1030        # self.set_d(SPC_DDS_CORE0_FREQ + core_index, float(frequency))

set the frequency of the sine wave of a specific core (see register SPC_DDS_CORE0_FREQ in the manual)

Parameters
  • core_index (int (optional)): the index of the core to be changed
  • frequency (float): the value of the frequency in Hz
def get_freq(self, core_index: int, return_unit=None) -> float:
1034    def get_freq(self, core_index : int, return_unit = None) -> float:
1035        """
1036        gets the frequency of the sine wave of a specific core (see register `SPC_DDS_CORE0_FREQ` in the manual)
1037        
1038        Parameters
1039        ----------
1040        core_index : int
1041            the index of the core to be changed
1042        return_unit : pint.Unit = None
1043            the unit of the returned frequency, by default None
1044        
1045        Returns
1046        -------
1047        float | pint.Quantity
1048            the value of the frequency in Hz the specific core or in the specified unit
1049        """
1050
1051        return self.cores[core_index].get_freq(return_unit)

gets the frequency of the sine wave of a specific core (see register SPC_DDS_CORE0_FREQ in the manual)

Parameters
  • core_index (int): the index of the core to be changed
  • return_unit (pint.Unit = None): the unit of the returned frequency, by default None
Returns
  • float | pint.Quantity: the value of the frequency in Hz the specific core or in the specified unit
def get_frequency(self, core_index: int, return_unit=None) -> float:
1034    def get_freq(self, core_index : int, return_unit = None) -> float:
1035        """
1036        gets the frequency of the sine wave of a specific core (see register `SPC_DDS_CORE0_FREQ` in the manual)
1037        
1038        Parameters
1039        ----------
1040        core_index : int
1041            the index of the core to be changed
1042        return_unit : pint.Unit = None
1043            the unit of the returned frequency, by default None
1044        
1045        Returns
1046        -------
1047        float | pint.Quantity
1048            the value of the frequency in Hz the specific core or in the specified unit
1049        """
1050
1051        return self.cores[core_index].get_freq(return_unit)

gets the frequency of the sine wave of a specific core (see register SPC_DDS_CORE0_FREQ in the manual)

Parameters
  • core_index (int): the index of the core to be changed
  • return_unit (pint.Unit = None): the unit of the returned frequency, by default None
Returns
  • float | pint.Quantity: the value of the frequency in Hz the specific core or in the specified unit
def avail_freq_min(self) -> float:
1055    def avail_freq_min(self) -> float:
1056        """
1057        get the minimum available frequency (see register `SPC_DDS_AVAIL_FREQ_MIN` in the manual)
1058
1059        Returns
1060        -------
1061        float
1062            the minimum available frequency
1063        
1064        TODO: unitize!
1065        """
1066
1067        return self.card.get_d(SPC_DDS_AVAIL_FREQ_MIN)

get the minimum available frequency (see register SPC_DDS_AVAIL_FREQ_MIN in the manual)

Returns
  • float: the minimum available frequency
  • TODO (unitize!):
def avail_freq_max(self) -> float:
1069    def avail_freq_max(self) -> float:
1070        """
1071        get the maximum available frequency (see register `SPC_DDS_AVAIL_FREQ_MAX` in the manual)
1072
1073        Returns
1074        -------
1075        float
1076            the maximum available frequency
1077        
1078        TODO: unitize!
1079        """
1080
1081        return self.card.get_d(SPC_DDS_AVAIL_FREQ_MAX)

get the maximum available frequency (see register SPC_DDS_AVAIL_FREQ_MAX in the manual)

Returns
  • float: the maximum available frequency
  • TODO (unitize!):
def avail_freq_step(self) -> float:
1083    def avail_freq_step(self) -> float:
1084        """
1085        get the step size of the available frequencies (see register `SPC_DDS_AVAIL_FREQ_STEP` in the manual)
1086
1087        Returns
1088        -------
1089        float
1090            the step size of the available frequencies
1091        
1092        TODO: unitize!
1093        """
1094
1095        return self.card.get_d(SPC_DDS_AVAIL_FREQ_STEP)

get the step size of the available frequencies (see register SPC_DDS_AVAIL_FREQ_STEP in the manual)

Returns
  • float: the step size of the available frequencies
  • TODO (unitize!):
def phase(self, *args) -> None:
1098    def phase(self, *args) -> None:
1099        """
1100        set the phase of the sine wave of a specific core (see register `SPC_DDS_CORE0_PHASE` in the manual)
1101        
1102        Parameters
1103        ----------
1104        core_index : int (optional)
1105            the index of the core to be changed
1106        phase : float
1107            the value between 0 and 360 degrees of the phase
1108        """
1109
1110        if len(args) == 1:
1111            phase = args[0]
1112            for core in self.cores:
1113                core.phase(phase)
1114        elif len(args) == 2:
1115            core_index, phase = args
1116            self.cores[core_index].phase(phase)
1117        else:
1118            raise TypeError("phase() takes 1 or 2 positional arguments ({} given)".format(len(args) + 1))
1119        # self.set_d(SPC_DDS_CORE0_PHASE + core_index, float(phase))

set the phase of the sine wave of a specific core (see register SPC_DDS_CORE0_PHASE in the manual)

Parameters
  • core_index (int (optional)): the index of the core to be changed
  • phase (float): the value between 0 and 360 degrees of the phase
def get_phase(self, core_index: int, return_unit=None) -> float:
1121    def get_phase(self, core_index : int, return_unit = None) -> float:
1122        """
1123        gets the phase of the sine wave of a specific core (see register `SPC_DDS_CORE0_PHASE` in the manual)
1124        
1125        Parameters
1126        ----------
1127        core_index : int
1128            the index of the core to be changed
1129        return_unit : pint.Unit = None
1130            the unit of the returned phase, by default None
1131        
1132        Returns
1133        -------
1134        float
1135            the value between 0 and 360 degrees of the phase
1136        """
1137
1138        return self.cores[core_index].get_phase(return_unit)

gets the phase of the sine wave of a specific core (see register SPC_DDS_CORE0_PHASE in the manual)

Parameters
  • core_index (int): the index of the core to be changed
  • return_unit (pint.Unit = None): the unit of the returned phase, by default None
Returns
  • float: the value between 0 and 360 degrees of the phase
def avail_phase_min(self) -> float:
1140    def avail_phase_min(self) -> float:
1141        """
1142        get the minimum available phase (see register `SPC_DDS_AVAIL_PHASE_MIN` in the manual)
1143
1144        Returns
1145        -------
1146        float
1147            the minimum available phase
1148        
1149        TODO: unitize!
1150        """
1151
1152        return self.card.get_d(SPC_DDS_AVAIL_PHASE_MIN)

get the minimum available phase (see register SPC_DDS_AVAIL_PHASE_MIN in the manual)

Returns
  • float: the minimum available phase
  • TODO (unitize!):
def avail_phase_max(self) -> float:
1154    def avail_phase_max(self) -> float:
1155        """
1156        get the maximum available phase (see register `SPC_DDS_AVAIL_PHASE_MAX` in the manual)
1157
1158        Returns
1159        -------
1160        float
1161            the maximum available phase
1162        
1163        TODO: unitize!
1164        """
1165
1166        return self.card.get_d(SPC_DDS_AVAIL_PHASE_MAX)

get the maximum available phase (see register SPC_DDS_AVAIL_PHASE_MAX in the manual)

Returns
  • float: the maximum available phase
  • TODO (unitize!):
def avail_phase_step(self) -> float:
1168    def avail_phase_step(self) -> float:
1169        """
1170        get the step size of the available phases (see register `SPC_DDS_AVAIL_PHASE_STEP` in the manual)
1171
1172        Returns
1173        -------
1174        float
1175            the step size of the available phases
1176        
1177        TODO: unitize!
1178        """
1179
1180        return self.card.get_d(SPC_DDS_AVAIL_PHASE_STEP)

get the step size of the available phases (see register SPC_DDS_AVAIL_PHASE_STEP in the manual)

Returns
  • float: the step size of the available phases
  • TODO (unitize!):
def x_manual_output(self, state_mask: int) -> None:
1182    def x_manual_output(self, state_mask : int) -> None:
1183        """
1184        set the output of the xio channels using a bit mask (see register `SPC_DDS_X_MANUAL_OUTPUT` in the manual)
1185        
1186        Parameters
1187        ----------
1188        state_mask : int
1189            bit mask where the bits correspond to specific channels and 1 to on and 0 to off.
1190        """
1191
1192        self.set_i(SPC_DDS_X_MANUAL_OUTPUT, state_mask)

set the output of the xio channels using a bit mask (see register SPC_DDS_X_MANUAL_OUTPUT in the manual)

Parameters
  • state_mask (int): bit mask where the bits correspond to specific channels and 1 to on and 0 to off.
def get_x_manual_output(self) -> int:
1194    def get_x_manual_output(self) -> int:
1195        """
1196        get the output of the xio channels using a bit mask (see register `SPC_DDS_X_MANUAL_OUTPUT` in the manual)
1197        
1198        Returns
1199        ----------
1200        int
1201            bit mask where the bits correspond to specific channels and 1 to on and 0 to off.
1202        """
1203
1204        return self.card.get_i(SPC_DDS_X_MANUAL_OUTPUT)

get the output of the xio channels using a bit mask (see register SPC_DDS_X_MANUAL_OUTPUT in the manual)

Returns
  • int: bit mask where the bits correspond to specific channels and 1 to on and 0 to off.
def freq_slope(self, *args) -> None:
1208    def freq_slope(self, *args) -> None:
1209        """
1210        set the frequency slope of the linearly changing frequency of the sine wave of a specific core (see register `SPC_DDS_CORE0_FREQ_SLOPE` in the manual)
1211        
1212        Parameters
1213        ----------
1214        core_index : int (optional)
1215            the index of the core to be changed
1216        slope : float
1217            the rate of frequency change in Hz/s
1218        """
1219
1220        if len(args) == 1:
1221            slope = args[0]
1222            for core in self.cores:
1223                core.freq_slope(slope)
1224        elif len(args) == 2:
1225            core_index, slope = args
1226            self.cores[core_index].freq_slope(slope)
1227        else:
1228            raise TypeError("freq_slope() takes 1 or 2 positional arguments ({} given)".format(len(args) + 1))
1229        # self.set_d(SPC_DDS_CORE0_FREQ_SLOPE + core_index, float(slope))

set the frequency slope of the linearly changing frequency of the sine wave of a specific core (see register SPC_DDS_CORE0_FREQ_SLOPE in the manual)

Parameters
  • core_index (int (optional)): the index of the core to be changed
  • slope (float): the rate of frequency change in Hz/s
def frequency_slope(self, *args) -> None:
1208    def freq_slope(self, *args) -> None:
1209        """
1210        set the frequency slope of the linearly changing frequency of the sine wave of a specific core (see register `SPC_DDS_CORE0_FREQ_SLOPE` in the manual)
1211        
1212        Parameters
1213        ----------
1214        core_index : int (optional)
1215            the index of the core to be changed
1216        slope : float
1217            the rate of frequency change in Hz/s
1218        """
1219
1220        if len(args) == 1:
1221            slope = args[0]
1222            for core in self.cores:
1223                core.freq_slope(slope)
1224        elif len(args) == 2:
1225            core_index, slope = args
1226            self.cores[core_index].freq_slope(slope)
1227        else:
1228            raise TypeError("freq_slope() takes 1 or 2 positional arguments ({} given)".format(len(args) + 1))
1229        # self.set_d(SPC_DDS_CORE0_FREQ_SLOPE + core_index, float(slope))

set the frequency slope of the linearly changing frequency of the sine wave of a specific core (see register SPC_DDS_CORE0_FREQ_SLOPE in the manual)

Parameters
  • core_index (int (optional)): the index of the core to be changed
  • slope (float): the rate of frequency change in Hz/s
def get_freq_slope(self, core_index: int, return_unit=None) -> float:
1233    def get_freq_slope(self, core_index : int, return_unit=None) -> float:
1234        """
1235        get the frequency slope of the linearly changing frequency of the sine wave of a specific core (see register `SPC_DDS_CORE0_FREQ_SLOPE` in the manual)
1236        
1237        Parameters
1238        ----------
1239        core_index : int
1240            the index of the core to be changed
1241        return_unit : pint.Unit = None
1242            the unit of the returned frequency slope, by default None
1243        
1244        Returns
1245        -------
1246        float
1247            the rate of frequency change in Hz/s
1248        """
1249
1250        return self.cores[core_index].get_freq_slope(return_unit)

get the frequency slope of the linearly changing frequency of the sine wave of a specific core (see register SPC_DDS_CORE0_FREQ_SLOPE in the manual)

Parameters
  • core_index (int): the index of the core to be changed
  • return_unit (pint.Unit = None): the unit of the returned frequency slope, by default None
Returns
  • float: the rate of frequency change in Hz/s
def get_frequency_slope(self, core_index: int, return_unit=None) -> float:
1233    def get_freq_slope(self, core_index : int, return_unit=None) -> float:
1234        """
1235        get the frequency slope of the linearly changing frequency of the sine wave of a specific core (see register `SPC_DDS_CORE0_FREQ_SLOPE` in the manual)
1236        
1237        Parameters
1238        ----------
1239        core_index : int
1240            the index of the core to be changed
1241        return_unit : pint.Unit = None
1242            the unit of the returned frequency slope, by default None
1243        
1244        Returns
1245        -------
1246        float
1247            the rate of frequency change in Hz/s
1248        """
1249
1250        return self.cores[core_index].get_freq_slope(return_unit)

get the frequency slope of the linearly changing frequency of the sine wave of a specific core (see register SPC_DDS_CORE0_FREQ_SLOPE in the manual)

Parameters
  • core_index (int): the index of the core to be changed
  • return_unit (pint.Unit = None): the unit of the returned frequency slope, by default None
Returns
  • float: the rate of frequency change in Hz/s
def avail_freq_slope_min(self) -> float:
1254    def avail_freq_slope_min(self) -> float:
1255        """
1256        get the minimum available frequency slope (see register `SPC_DDS_AVAIL_FREQ_SLOPE_MIN` in the manual)
1257
1258        Returns
1259        -------
1260        float
1261            the minimum available frequency slope
1262        
1263        TODO: unitize!
1264        """
1265
1266        return self.card.get_d(SPC_DDS_AVAIL_FREQ_SLOPE_MIN)

get the minimum available frequency slope (see register SPC_DDS_AVAIL_FREQ_SLOPE_MIN in the manual)

Returns
  • float: the minimum available frequency slope
  • TODO (unitize!):
def avail_freq_slope_max(self) -> float:
1268    def avail_freq_slope_max(self) -> float:
1269        """
1270        get the maximum available frequency slope (see register `SPC_DDS_AVAIL_FREQ_SLOPE_MAX` in the manual)
1271
1272        Returns
1273        -------
1274        float
1275            the maximum available frequency slope
1276        
1277        TODO: unitize!
1278        """
1279
1280        return self.card.get_d(SPC_DDS_AVAIL_FREQ_SLOPE_MAX)

get the maximum available frequency slope (see register SPC_DDS_AVAIL_FREQ_SLOPE_MAX in the manual)

Returns
  • float: the maximum available frequency slope
  • TODO (unitize!):
def avail_freq_slope_step(self) -> float:
1282    def avail_freq_slope_step(self) -> float:
1283        """
1284        get the step size of the available frequency slopes (see register `SPC_DDS_AVAIL_FREQ_SLOPE_STEP` in the manual)
1285
1286        Returns
1287        -------
1288        float
1289            the step size of the available frequency slopes
1290        
1291        TODO: unitize!
1292        """
1293
1294        return self.card.get_d(SPC_DDS_AVAIL_FREQ_SLOPE_STEP)

get the step size of the available frequency slopes (see register SPC_DDS_AVAIL_FREQ_SLOPE_STEP in the manual)

Returns
  • float: the step size of the available frequency slopes
  • TODO (unitize!):
def amp_slope(self, *args) -> None:
1297    def amp_slope(self, *args) -> None:
1298        """
1299        set the amplitude slope of the linearly changing amplitude of the sine wave of a specific core (see register `SPC_DDS_CORE0_AMP_SLOPE` in the manual)
1300        
1301        Parameters
1302        ----------
1303        core_index : int (optional)
1304            the index of the core to be changed
1305        slope : float
1306            the rate of amplitude change in 1/s
1307        """
1308
1309        if len(args) == 1:
1310            slope = args[0]
1311            for core in self.cores:
1312                core.amp_slope(slope)
1313        elif len(args) == 2:
1314            core_index, slope = args
1315            self.cores[core_index].amp_slope(slope)
1316        else:
1317            raise TypeError("amp_slope() takes 1 or 2 positional arguments ({} given)".format(len(args) + 1))
1318        # self.set_d(SPC_DDS_CORE0_AMP_SLOPE + core_index, float(slope))

set the amplitude slope of the linearly changing amplitude of the sine wave of a specific core (see register SPC_DDS_CORE0_AMP_SLOPE in the manual)

Parameters
  • core_index (int (optional)): the index of the core to be changed
  • slope (float): the rate of amplitude change in 1/s
def amplitude_slope(self, *args) -> None:
1297    def amp_slope(self, *args) -> None:
1298        """
1299        set the amplitude slope of the linearly changing amplitude of the sine wave of a specific core (see register `SPC_DDS_CORE0_AMP_SLOPE` in the manual)
1300        
1301        Parameters
1302        ----------
1303        core_index : int (optional)
1304            the index of the core to be changed
1305        slope : float
1306            the rate of amplitude change in 1/s
1307        """
1308
1309        if len(args) == 1:
1310            slope = args[0]
1311            for core in self.cores:
1312                core.amp_slope(slope)
1313        elif len(args) == 2:
1314            core_index, slope = args
1315            self.cores[core_index].amp_slope(slope)
1316        else:
1317            raise TypeError("amp_slope() takes 1 or 2 positional arguments ({} given)".format(len(args) + 1))
1318        # self.set_d(SPC_DDS_CORE0_AMP_SLOPE + core_index, float(slope))

set the amplitude slope of the linearly changing amplitude of the sine wave of a specific core (see register SPC_DDS_CORE0_AMP_SLOPE in the manual)

Parameters
  • core_index (int (optional)): the index of the core to be changed
  • slope (float): the rate of amplitude change in 1/s
def get_amp_slope(self, core_index: int, return_unit=None) -> float:
1322    def get_amp_slope(self, core_index : int, return_unit = None) -> float:
1323        """
1324        set the amplitude slope of the linearly changing amplitude of the sine wave of a specific core (see register `SPC_DDS_CORE0_AMP_SLOPE` in the manual)
1325        
1326        Parameters
1327        ----------
1328        core_index : int
1329            the index of the core to be changed
1330        return_unit : pint.Unit = None
1331            the unit of the returned amplitude slope, by default None
1332        
1333        Returns
1334        -------
1335        float
1336            the rate of amplitude change in 1/s
1337        """
1338
1339        return self.cores[core_index].get_amp_slope(return_unit)

set the amplitude slope of the linearly changing amplitude of the sine wave of a specific core (see register SPC_DDS_CORE0_AMP_SLOPE in the manual)

Parameters
  • core_index (int): the index of the core to be changed
  • return_unit (pint.Unit = None): the unit of the returned amplitude slope, by default None
Returns
  • float: the rate of amplitude change in 1/s
def avail_amp_slope_min(self) -> float:
1343    def avail_amp_slope_min(self) -> float:
1344        """
1345        get the minimum available amplitude slope (see register `SPC_DDS_AVAIL_AMP_SLOPE_MIN` in the manual)
1346
1347        Returns
1348        -------
1349        float
1350            the minimum available amplitude slope
1351        
1352        TODO: unitize!
1353        """
1354
1355        return self.card.get_d(SPC_DDS_AVAIL_AMP_SLOPE_MIN)

get the minimum available amplitude slope (see register SPC_DDS_AVAIL_AMP_SLOPE_MIN in the manual)

Returns
  • float: the minimum available amplitude slope
  • TODO (unitize!):
def avail_amp_slope_max(self) -> float:
1357    def avail_amp_slope_max(self) -> float:
1358        """
1359        get the maximum available amplitude slope (see register `SPC_DDS_AVAIL_AMP_SLOPE_MAX` in the manual)
1360
1361        Returns
1362        -------
1363        float
1364            the maximum available amplitude slope
1365        
1366        TODO: unitize!
1367        """
1368
1369        return self.card.get_d(SPC_DDS_AVAIL_AMP_SLOPE_MAX)

get the maximum available amplitude slope (see register SPC_DDS_AVAIL_AMP_SLOPE_MAX in the manual)

Returns
  • float: the maximum available amplitude slope
  • TODO (unitize!):
def avail_amp_slope_step(self) -> float:
1371    def avail_amp_slope_step(self) -> float:
1372        """
1373        get the step size of the available amplitude slopes (see register `SPC_DDS_AVAIL_AMP_SLOPE_STEP` in the manual)
1374
1375        Returns
1376        -------
1377        float
1378            the step size of the available amplitude slopes
1379        
1380        TODO: unitize!
1381        """
1382
1383        return self.card.get_d(SPC_DDS_AVAIL_AMP_SLOPE_STEP)

get the step size of the available amplitude slopes (see register SPC_DDS_AVAIL_AMP_SLOPE_STEP in the manual)

Returns
  • float: the step size of the available amplitude slopes
  • TODO (unitize!):
def cmd(self, command: int) -> None:
1386    def cmd(self, command : int) -> None:
1387        """
1388        execute a DDS specific control flow command (see register `SPC_DDS_CMD` in the manual)
1389        
1390        Parameters
1391        ----------
1392        command : int
1393            DDS specific command
1394        """
1395
1396        self.set_i(SPC_DDS_CMD, command)

execute a DDS specific control flow command (see register SPC_DDS_CMD in the manual)

Parameters
  • command (int): DDS specific command
def exec_at_trg(self) -> None:
1398    def exec_at_trg(self) -> None:
1399        """
1400        execute the commands in the shadow register at the next trigger event (see register `SPC_DDS_CMD` in the manual)
1401        """
1402        self.cmd(SPCM_DDS_CMD_EXEC_AT_TRG)

execute the commands in the shadow register at the next trigger event (see register SPC_DDS_CMD in the manual)

def arm(self) -> None:
1398    def exec_at_trg(self) -> None:
1399        """
1400        execute the commands in the shadow register at the next trigger event (see register `SPC_DDS_CMD` in the manual)
1401        """
1402        self.cmd(SPCM_DDS_CMD_EXEC_AT_TRG)

execute the commands in the shadow register at the next trigger event (see register SPC_DDS_CMD in the manual)

def wait_for_trg(self) -> None:
1398    def exec_at_trg(self) -> None:
1399        """
1400        execute the commands in the shadow register at the next trigger event (see register `SPC_DDS_CMD` in the manual)
1401        """
1402        self.cmd(SPCM_DDS_CMD_EXEC_AT_TRG)

execute the commands in the shadow register at the next trigger event (see register SPC_DDS_CMD in the manual)

def exec_now(self) -> None:
1407    def exec_now(self) -> None:
1408        """
1409        execute the commands in the shadow register as soon as possible (see register `SPC_DDS_CMD` in the manual)
1410        """
1411
1412        self.cmd(SPCM_DDS_CMD_EXEC_NOW)

execute the commands in the shadow register as soon as possible (see register SPC_DDS_CMD in the manual)

def direct_latch(self) -> None:
1407    def exec_now(self) -> None:
1408        """
1409        execute the commands in the shadow register as soon as possible (see register `SPC_DDS_CMD` in the manual)
1410        """
1411
1412        self.cmd(SPCM_DDS_CMD_EXEC_NOW)

execute the commands in the shadow register as soon as possible (see register SPC_DDS_CMD in the manual)

def trg_count(self) -> int:
1416    def trg_count(self) -> int:
1417        """
1418        get the number of trigger exec_at_trg and exec_now command that have been executed (see register `SPC_DDS_TRG_COUNT` in the manual)
1419
1420        Returns
1421        -------
1422        int
1423            the number of trigger exec_at_trg and exec_now command that have been executed
1424        """
1425
1426        return self.card.get_i(SPC_DDS_TRG_COUNT)

get the number of trigger exec_at_trg and exec_now command that have been executed (see register SPC_DDS_TRG_COUNT in the manual)

Returns
  • int: the number of trigger exec_at_trg and exec_now command that have been executed
def write_to_card(self, flags=0) -> None:
1428    def write_to_card(self, flags=0) -> None:
1429        """
1430        send a list of all the commands that came after the last write_list and send them to the card (see register `SPC_DDS_CMD` in the manual)
1431
1432        Parameters
1433        ----------
1434        flags : int = 0
1435            the flags that can be set with the write_to_card command
1436        """
1437        
1438        self.cmd(SPCM_DDS_CMD_WRITE_TO_CARD | flags)

send a list of all the commands that came after the last write_list and send them to the card (see register SPC_DDS_CMD in the manual)

Parameters
  • flags (int = 0): the flags that can be set with the write_to_card command
def kwargs2mask(self, kwargs: dict[str, bool], prefix: str = '') -> int:
1441    def kwargs2mask(self, kwargs : dict[str, bool], prefix : str = "") -> int:
1442        """
1443        DDS helper: transform a dictionary with keys with a specific prefix to a bitmask
1444
1445        Parameters
1446        ----------
1447        kwargs : dict
1448            dictonary with keys with a specific prefix and values given by bools
1449        prefix : str
1450            a prefix for the key names
1451        
1452        Returns
1453        -------
1454        int
1455            bit mask
1456        
1457        Example
1458        -------
1459        ['core_0' = True, 'core_2' = False, 'core_3' = True] => 0b1001 = 9
1460        """
1461        
1462        mask = 0
1463        for keyword, value in kwargs.items():
1464            bit = int(keyword[len(prefix):])
1465            if value:
1466                mask |= 1 << bit
1467            else:
1468                mask &= ~(1 << bit)
1469        return mask

DDS helper: transform a dictionary with keys with a specific prefix to a bitmask

Parameters
  • kwargs (dict): dictonary with keys with a specific prefix and values given by bools
  • prefix (str): a prefix for the key names
Returns
  • int: bit mask
Example

['core_0' = True, 'core_2' = False, 'core_3' = True] => 0b1001 = 9

def k2m(self, kwargs: dict[str, bool], prefix: str = '') -> int:
1441    def kwargs2mask(self, kwargs : dict[str, bool], prefix : str = "") -> int:
1442        """
1443        DDS helper: transform a dictionary with keys with a specific prefix to a bitmask
1444
1445        Parameters
1446        ----------
1447        kwargs : dict
1448            dictonary with keys with a specific prefix and values given by bools
1449        prefix : str
1450            a prefix for the key names
1451        
1452        Returns
1453        -------
1454        int
1455            bit mask
1456        
1457        Example
1458        -------
1459        ['core_0' = True, 'core_2' = False, 'core_3' = True] => 0b1001 = 9
1460        """
1461        
1462        mask = 0
1463        for keyword, value in kwargs.items():
1464            bit = int(keyword[len(prefix):])
1465            if value:
1466                mask |= 1 << bit
1467            else:
1468                mask &= ~(1 << bit)
1469        return mask

DDS helper: transform a dictionary with keys with a specific prefix to a bitmask

Parameters
  • kwargs (dict): dictonary with keys with a specific prefix and values given by bools
  • prefix (str): a prefix for the key names
Returns
  • int: bit mask
Example

['core_0' = True, 'core_2' = False, 'core_3' = True] => 0b1001 = 9

class DDSCore(spcm.classes_dds.DDSBareCore):
210class DDSCore(DDSBareCore):
211    """
212    a class for controlling a single DDS core
213    """
214
215    channel : Channel
216
217    def __init__(self, core_index, dds, *args, **kwargs) -> None:
218        super().__init__(core_index, dds, *args, **kwargs)
219        self.channel = kwargs.get("channel", None)
220    
221    # DDS "static" parameters
222    def amp(self, amplitude : float, *args, **kwargs) -> None:
223        """
224        set the amplitude of the sine wave of a specific core (see register `SPC_DDS_CORE0_AMP` in the manual)
225        
226        Parameters
227        ----------
228        amplitude : float | pint.Quantity
229            the value between 0 and 1 corresponding to the amplitude
230        """
231
232        if self.channel is not None:
233            amplitude = self.channel.to_amplitude_fraction(amplitude)
234        elif isinstance(amplitude, units.Quantity) and amplitude.check("[]"):
235            amplitude = UnitConversion.convert(amplitude, units.fraction, float, rounding=None)
236        super().amp(amplitude)
237    # aliases
238    amplitude = amp
239
240    def get_amp(self, return_unit = None, *args, **kwargs) -> float:
241        """
242        gets the amplitude of the sine wave of a specific core (see register `SPC_DDS_CORE0_AMP` in the manual)
243
244        Parameters
245        ----------
246        return_unit : pint.Unit = None
247            the unit of the returned amplitude, by default None
248
249        Returns
250        -------
251        float
252            the value between 0 and 1 corresponding to the amplitude
253        """
254
255        return_value = super().get_amp()
256        if self.channel is not None:
257            return_value = self.channel.from_amplitude_fraction(return_value, return_unit)
258        else:
259            return_value = UnitConversion.to_unit(return_value, return_unit)
260        return return_value
261    # aliases
262    get_amplitude = get_amp
263
264    def freq(self, frequency : float, *args, **kwargs) -> None:
265        """
266        set the frequency of the sine wave of a specific core (see register `SPC_DDS_CORE0_FREQ` in the manual)
267        
268        Parameters
269        ----------
270        frequency : float | pint.Quantity
271            the value of the frequency in Hz
272        """
273
274        frequency = UnitConversion.convert(frequency, units.Hz, float, rounding=None)
275        super().freq(frequency)
276    # aliases
277    frequency = freq
278
279    def get_freq(self, return_unit = None, *args, **kwargs) -> float:
280        """
281        gets the frequency of the sine wave of a specific core (see register `SPC_DDS_CORE0_FREQ` in the manual)
282        
283        Parameters
284        ----------
285        return_unit : pint.Unit = None
286            the unit of the returned frequency, by default None
287
288        Returns
289        -------
290        float | pint.Quantity
291            the value of the frequency in Hz the specific core
292        """
293
294        return_value = super().get_freq()
295        if return_unit is not None: return_value = UnitConversion.to_unit(return_value * units.Hz, return_unit)
296        return return_value
297    # aliases
298    get_frequency = get_freq
299
300    def phase(self, phase : float, *args, **kwargs) -> None:
301        """
302        set the phase of the sine wave of a specific core (see register `SPC_DDS_CORE0_PHASE` in the manual)
303        
304        Parameters
305        ----------
306        phase : float | pint.Quantity
307            the value between 0 and 360 degrees of the phase
308        """
309
310        phase = UnitConversion.convert(phase, units.deg, float, rounding=None)
311        super().phase(phase)
312
313    def get_phase(self, return_unit = None, *args, **kwargs) -> float:
314        """
315        gets the phase of the sine wave of a specific core (see register `SPC_DDS_CORE0_PHASE` in the manual)
316        
317        Returns
318        -------
319        float
320            the value between 0 and 360 degrees of the phase
321        """
322
323        return_value = super().get_phase()
324        if return_unit is not None: return_value = UnitConversion.to_unit(return_value * units.deg, return_unit)
325        return return_value
326
327    # DDS dynamic parameters
328    def freq_slope(self, slope : float, *args, **kwargs) -> None:
329        """
330        set the frequency slope of the linearly changing frequency of the sine wave of a specific core (see register `SPC_DDS_CORE0_FREQ_SLOPE` in the manual)
331        
332        Parameters
333        ----------
334        slope : float | pint.Quantity
335            the rate of frequency change in Hz/s (positive or negative) or specified unit
336        """
337
338        slope = UnitConversion.convert(slope, units.Hz/units.s, float, rounding=None)
339        super().freq_slope(slope)
340    # aliases
341    frequency_slope = freq_slope
342
343    def get_freq_slope(self, return_unit = None, *args, **kwargs) -> float:
344        """
345        get the frequency slope of the linearly changing frequency of the sine wave of a specific core (see register `SPC_DDS_CORE0_FREQ_SLOPE` in the manual)
346        
347        Parameters
348        ----------
349        return_unit : pint.Unit = None
350            the unit of the returned frequency slope, by default None
351
352        Returns
353        -------
354        float
355            the rate of frequency change in Hz/s
356        """
357
358        return_value = super().get_freq_slope()
359        if return_unit is not None: return_value = UnitConversion.to_unit(return_value * units.Hz/units.s, return_unit)
360        return return_value
361    # aliases
362    get_frequency_slope = get_freq_slope
363
364    def amp_slope(self, slope : float, *args, **kwargs) -> None:
365        """
366        set the amplitude slope of the linearly changing amplitude of the sine wave of a specific core (see register `SPC_DDS_CORE0_AMP_SLOPE` in the manual)
367        
368        Parameters
369        ----------
370        slope : float | pint.Quantity
371            the rate of amplitude change in 1/s (positive or negative) or specified unit
372        """
373
374        slope = UnitConversion.convert(slope, 1/units.s, float, rounding=None)
375        super().amp_slope(slope)
376    # aliases
377    amplitude_slope = amp_slope
378
379    def get_amp_slope(self, return_unit = None, *args, **kwargs) -> float:
380        """
381        set the amplitude slope of the linearly changing amplitude of the sine wave of a specific core (see register `SPC_DDS_CORE0_AMP_SLOPE` in the manual)
382        
383        Parameters
384        ----------
385        return_unit : pint.Unit = None
386            the unit of the returned amplitude slope, by default None
387
388        Returns
389        -------
390        float
391            the rate of amplitude change in 1/s
392        """
393
394
395        return_value = super().get_amp_slope()
396        if return_unit is not None: return_value = UnitConversion.to_unit(return_value / units.s, return_unit)
397        return return_value
398    # aliases
399    get_amplitude_slope = get_amp_slope

a class for controlling a single DDS core

DDSCore(core_index, dds, *args, **kwargs)
217    def __init__(self, core_index, dds, *args, **kwargs) -> None:
218        super().__init__(core_index, dds, *args, **kwargs)
219        self.channel = kwargs.get("channel", None)
channel: Channel
def amp(self, amplitude: float, *args, **kwargs) -> None:
222    def amp(self, amplitude : float, *args, **kwargs) -> None:
223        """
224        set the amplitude of the sine wave of a specific core (see register `SPC_DDS_CORE0_AMP` in the manual)
225        
226        Parameters
227        ----------
228        amplitude : float | pint.Quantity
229            the value between 0 and 1 corresponding to the amplitude
230        """
231
232        if self.channel is not None:
233            amplitude = self.channel.to_amplitude_fraction(amplitude)
234        elif isinstance(amplitude, units.Quantity) and amplitude.check("[]"):
235            amplitude = UnitConversion.convert(amplitude, units.fraction, float, rounding=None)
236        super().amp(amplitude)

set the amplitude of the sine wave of a specific core (see register SPC_DDS_CORE0_AMP in the manual)

Parameters
  • amplitude (float | pint.Quantity): the value between 0 and 1 corresponding to the amplitude
def amplitude(self, amplitude: float, *args, **kwargs) -> None:
222    def amp(self, amplitude : float, *args, **kwargs) -> None:
223        """
224        set the amplitude of the sine wave of a specific core (see register `SPC_DDS_CORE0_AMP` in the manual)
225        
226        Parameters
227        ----------
228        amplitude : float | pint.Quantity
229            the value between 0 and 1 corresponding to the amplitude
230        """
231
232        if self.channel is not None:
233            amplitude = self.channel.to_amplitude_fraction(amplitude)
234        elif isinstance(amplitude, units.Quantity) and amplitude.check("[]"):
235            amplitude = UnitConversion.convert(amplitude, units.fraction, float, rounding=None)
236        super().amp(amplitude)

set the amplitude of the sine wave of a specific core (see register SPC_DDS_CORE0_AMP in the manual)

Parameters
  • amplitude (float | pint.Quantity): the value between 0 and 1 corresponding to the amplitude
def get_amp(self, return_unit=None, *args, **kwargs) -> float:
240    def get_amp(self, return_unit = None, *args, **kwargs) -> float:
241        """
242        gets the amplitude of the sine wave of a specific core (see register `SPC_DDS_CORE0_AMP` in the manual)
243
244        Parameters
245        ----------
246        return_unit : pint.Unit = None
247            the unit of the returned amplitude, by default None
248
249        Returns
250        -------
251        float
252            the value between 0 and 1 corresponding to the amplitude
253        """
254
255        return_value = super().get_amp()
256        if self.channel is not None:
257            return_value = self.channel.from_amplitude_fraction(return_value, return_unit)
258        else:
259            return_value = UnitConversion.to_unit(return_value, return_unit)
260        return return_value

gets the amplitude of the sine wave of a specific core (see register SPC_DDS_CORE0_AMP in the manual)

Parameters
  • return_unit (pint.Unit = None): the unit of the returned amplitude, by default None
Returns
  • float: the value between 0 and 1 corresponding to the amplitude
def get_amplitude(self, return_unit=None, *args, **kwargs) -> float:
240    def get_amp(self, return_unit = None, *args, **kwargs) -> float:
241        """
242        gets the amplitude of the sine wave of a specific core (see register `SPC_DDS_CORE0_AMP` in the manual)
243
244        Parameters
245        ----------
246        return_unit : pint.Unit = None
247            the unit of the returned amplitude, by default None
248
249        Returns
250        -------
251        float
252            the value between 0 and 1 corresponding to the amplitude
253        """
254
255        return_value = super().get_amp()
256        if self.channel is not None:
257            return_value = self.channel.from_amplitude_fraction(return_value, return_unit)
258        else:
259            return_value = UnitConversion.to_unit(return_value, return_unit)
260        return return_value

gets the amplitude of the sine wave of a specific core (see register SPC_DDS_CORE0_AMP in the manual)

Parameters
  • return_unit (pint.Unit = None): the unit of the returned amplitude, by default None
Returns
  • float: the value between 0 and 1 corresponding to the amplitude
def freq(self, frequency: float, *args, **kwargs) -> None:
264    def freq(self, frequency : float, *args, **kwargs) -> None:
265        """
266        set the frequency of the sine wave of a specific core (see register `SPC_DDS_CORE0_FREQ` in the manual)
267        
268        Parameters
269        ----------
270        frequency : float | pint.Quantity
271            the value of the frequency in Hz
272        """
273
274        frequency = UnitConversion.convert(frequency, units.Hz, float, rounding=None)
275        super().freq(frequency)

set the frequency of the sine wave of a specific core (see register SPC_DDS_CORE0_FREQ in the manual)

Parameters
  • frequency (float | pint.Quantity): the value of the frequency in Hz
def frequency(self, frequency: float, *args, **kwargs) -> None:
264    def freq(self, frequency : float, *args, **kwargs) -> None:
265        """
266        set the frequency of the sine wave of a specific core (see register `SPC_DDS_CORE0_FREQ` in the manual)
267        
268        Parameters
269        ----------
270        frequency : float | pint.Quantity
271            the value of the frequency in Hz
272        """
273
274        frequency = UnitConversion.convert(frequency, units.Hz, float, rounding=None)
275        super().freq(frequency)

set the frequency of the sine wave of a specific core (see register SPC_DDS_CORE0_FREQ in the manual)

Parameters
  • frequency (float | pint.Quantity): the value of the frequency in Hz
def get_freq(self, return_unit=None, *args, **kwargs) -> float:
279    def get_freq(self, return_unit = None, *args, **kwargs) -> float:
280        """
281        gets the frequency of the sine wave of a specific core (see register `SPC_DDS_CORE0_FREQ` in the manual)
282        
283        Parameters
284        ----------
285        return_unit : pint.Unit = None
286            the unit of the returned frequency, by default None
287
288        Returns
289        -------
290        float | pint.Quantity
291            the value of the frequency in Hz the specific core
292        """
293
294        return_value = super().get_freq()
295        if return_unit is not None: return_value = UnitConversion.to_unit(return_value * units.Hz, return_unit)
296        return return_value

gets the frequency of the sine wave of a specific core (see register SPC_DDS_CORE0_FREQ in the manual)

Parameters
  • return_unit (pint.Unit = None): the unit of the returned frequency, by default None
Returns
  • float | pint.Quantity: the value of the frequency in Hz the specific core
def get_frequency(self, return_unit=None, *args, **kwargs) -> float:
279    def get_freq(self, return_unit = None, *args, **kwargs) -> float:
280        """
281        gets the frequency of the sine wave of a specific core (see register `SPC_DDS_CORE0_FREQ` in the manual)
282        
283        Parameters
284        ----------
285        return_unit : pint.Unit = None
286            the unit of the returned frequency, by default None
287
288        Returns
289        -------
290        float | pint.Quantity
291            the value of the frequency in Hz the specific core
292        """
293
294        return_value = super().get_freq()
295        if return_unit is not None: return_value = UnitConversion.to_unit(return_value * units.Hz, return_unit)
296        return return_value

gets the frequency of the sine wave of a specific core (see register SPC_DDS_CORE0_FREQ in the manual)

Parameters
  • return_unit (pint.Unit = None): the unit of the returned frequency, by default None
Returns
  • float | pint.Quantity: the value of the frequency in Hz the specific core
def phase(self, phase: float, *args, **kwargs) -> None:
300    def phase(self, phase : float, *args, **kwargs) -> None:
301        """
302        set the phase of the sine wave of a specific core (see register `SPC_DDS_CORE0_PHASE` in the manual)
303        
304        Parameters
305        ----------
306        phase : float | pint.Quantity
307            the value between 0 and 360 degrees of the phase
308        """
309
310        phase = UnitConversion.convert(phase, units.deg, float, rounding=None)
311        super().phase(phase)

set the phase of the sine wave of a specific core (see register SPC_DDS_CORE0_PHASE in the manual)

Parameters
  • phase (float | pint.Quantity): the value between 0 and 360 degrees of the phase
def get_phase(self, return_unit=None, *args, **kwargs) -> float:
313    def get_phase(self, return_unit = None, *args, **kwargs) -> float:
314        """
315        gets the phase of the sine wave of a specific core (see register `SPC_DDS_CORE0_PHASE` in the manual)
316        
317        Returns
318        -------
319        float
320            the value between 0 and 360 degrees of the phase
321        """
322
323        return_value = super().get_phase()
324        if return_unit is not None: return_value = UnitConversion.to_unit(return_value * units.deg, return_unit)
325        return return_value

gets the phase of the sine wave of a specific core (see register SPC_DDS_CORE0_PHASE in the manual)

Returns
  • float: the value between 0 and 360 degrees of the phase
def freq_slope(self, slope: float, *args, **kwargs) -> None:
328    def freq_slope(self, slope : float, *args, **kwargs) -> None:
329        """
330        set the frequency slope of the linearly changing frequency of the sine wave of a specific core (see register `SPC_DDS_CORE0_FREQ_SLOPE` in the manual)
331        
332        Parameters
333        ----------
334        slope : float | pint.Quantity
335            the rate of frequency change in Hz/s (positive or negative) or specified unit
336        """
337
338        slope = UnitConversion.convert(slope, units.Hz/units.s, float, rounding=None)
339        super().freq_slope(slope)

set the frequency slope of the linearly changing frequency of the sine wave of a specific core (see register SPC_DDS_CORE0_FREQ_SLOPE in the manual)

Parameters
  • slope (float | pint.Quantity): the rate of frequency change in Hz/s (positive or negative) or specified unit
def frequency_slope(self, slope: float, *args, **kwargs) -> None:
328    def freq_slope(self, slope : float, *args, **kwargs) -> None:
329        """
330        set the frequency slope of the linearly changing frequency of the sine wave of a specific core (see register `SPC_DDS_CORE0_FREQ_SLOPE` in the manual)
331        
332        Parameters
333        ----------
334        slope : float | pint.Quantity
335            the rate of frequency change in Hz/s (positive or negative) or specified unit
336        """
337
338        slope = UnitConversion.convert(slope, units.Hz/units.s, float, rounding=None)
339        super().freq_slope(slope)

set the frequency slope of the linearly changing frequency of the sine wave of a specific core (see register SPC_DDS_CORE0_FREQ_SLOPE in the manual)

Parameters
  • slope (float | pint.Quantity): the rate of frequency change in Hz/s (positive or negative) or specified unit
def get_freq_slope(self, return_unit=None, *args, **kwargs) -> float:
343    def get_freq_slope(self, return_unit = None, *args, **kwargs) -> float:
344        """
345        get the frequency slope of the linearly changing frequency of the sine wave of a specific core (see register `SPC_DDS_CORE0_FREQ_SLOPE` in the manual)
346        
347        Parameters
348        ----------
349        return_unit : pint.Unit = None
350            the unit of the returned frequency slope, by default None
351
352        Returns
353        -------
354        float
355            the rate of frequency change in Hz/s
356        """
357
358        return_value = super().get_freq_slope()
359        if return_unit is not None: return_value = UnitConversion.to_unit(return_value * units.Hz/units.s, return_unit)
360        return return_value

get the frequency slope of the linearly changing frequency of the sine wave of a specific core (see register SPC_DDS_CORE0_FREQ_SLOPE in the manual)

Parameters
  • return_unit (pint.Unit = None): the unit of the returned frequency slope, by default None
Returns
  • float: the rate of frequency change in Hz/s
def get_frequency_slope(self, return_unit=None, *args, **kwargs) -> float:
343    def get_freq_slope(self, return_unit = None, *args, **kwargs) -> float:
344        """
345        get the frequency slope of the linearly changing frequency of the sine wave of a specific core (see register `SPC_DDS_CORE0_FREQ_SLOPE` in the manual)
346        
347        Parameters
348        ----------
349        return_unit : pint.Unit = None
350            the unit of the returned frequency slope, by default None
351
352        Returns
353        -------
354        float
355            the rate of frequency change in Hz/s
356        """
357
358        return_value = super().get_freq_slope()
359        if return_unit is not None: return_value = UnitConversion.to_unit(return_value * units.Hz/units.s, return_unit)
360        return return_value

get the frequency slope of the linearly changing frequency of the sine wave of a specific core (see register SPC_DDS_CORE0_FREQ_SLOPE in the manual)

Parameters
  • return_unit (pint.Unit = None): the unit of the returned frequency slope, by default None
Returns
  • float: the rate of frequency change in Hz/s
def amp_slope(self, slope: float, *args, **kwargs) -> None:
364    def amp_slope(self, slope : float, *args, **kwargs) -> None:
365        """
366        set the amplitude slope of the linearly changing amplitude of the sine wave of a specific core (see register `SPC_DDS_CORE0_AMP_SLOPE` in the manual)
367        
368        Parameters
369        ----------
370        slope : float | pint.Quantity
371            the rate of amplitude change in 1/s (positive or negative) or specified unit
372        """
373
374        slope = UnitConversion.convert(slope, 1/units.s, float, rounding=None)
375        super().amp_slope(slope)

set the amplitude slope of the linearly changing amplitude of the sine wave of a specific core (see register SPC_DDS_CORE0_AMP_SLOPE in the manual)

Parameters
  • slope (float | pint.Quantity): the rate of amplitude change in 1/s (positive or negative) or specified unit
def amplitude_slope(self, slope: float, *args, **kwargs) -> None:
364    def amp_slope(self, slope : float, *args, **kwargs) -> None:
365        """
366        set the amplitude slope of the linearly changing amplitude of the sine wave of a specific core (see register `SPC_DDS_CORE0_AMP_SLOPE` in the manual)
367        
368        Parameters
369        ----------
370        slope : float | pint.Quantity
371            the rate of amplitude change in 1/s (positive or negative) or specified unit
372        """
373
374        slope = UnitConversion.convert(slope, 1/units.s, float, rounding=None)
375        super().amp_slope(slope)

set the amplitude slope of the linearly changing amplitude of the sine wave of a specific core (see register SPC_DDS_CORE0_AMP_SLOPE in the manual)

Parameters
  • slope (float | pint.Quantity): the rate of amplitude change in 1/s (positive or negative) or specified unit
def get_amp_slope(self, return_unit=None, *args, **kwargs) -> float:
379    def get_amp_slope(self, return_unit = None, *args, **kwargs) -> float:
380        """
381        set the amplitude slope of the linearly changing amplitude of the sine wave of a specific core (see register `SPC_DDS_CORE0_AMP_SLOPE` in the manual)
382        
383        Parameters
384        ----------
385        return_unit : pint.Unit = None
386            the unit of the returned amplitude slope, by default None
387
388        Returns
389        -------
390        float
391            the rate of amplitude change in 1/s
392        """
393
394
395        return_value = super().get_amp_slope()
396        if return_unit is not None: return_value = UnitConversion.to_unit(return_value / units.s, return_unit)
397        return return_value

set the amplitude slope of the linearly changing amplitude of the sine wave of a specific core (see register SPC_DDS_CORE0_AMP_SLOPE in the manual)

Parameters
  • return_unit (pint.Unit = None): the unit of the returned amplitude slope, by default None
Returns
  • float: the rate of amplitude change in 1/s
def get_amplitude_slope(self, return_unit=None, *args, **kwargs) -> float:
379    def get_amp_slope(self, return_unit = None, *args, **kwargs) -> float:
380        """
381        set the amplitude slope of the linearly changing amplitude of the sine wave of a specific core (see register `SPC_DDS_CORE0_AMP_SLOPE` in the manual)
382        
383        Parameters
384        ----------
385        return_unit : pint.Unit = None
386            the unit of the returned amplitude slope, by default None
387
388        Returns
389        -------
390        float
391            the rate of amplitude change in 1/s
392        """
393
394
395        return_value = super().get_amp_slope()
396        if return_unit is not None: return_value = UnitConversion.to_unit(return_value / units.s, return_unit)
397        return return_value

set the amplitude slope of the linearly changing amplitude of the sine wave of a specific core (see register SPC_DDS_CORE0_AMP_SLOPE in the manual)

Parameters
  • return_unit (pint.Unit = None): the unit of the returned amplitude slope, by default None
Returns
  • float: the rate of amplitude change in 1/s
class DDSCommandList(spcm.DDS):
 13class DDSCommandList(DDS):
 14    """Abstraction of the set_ptr and register `SPC_REGISTER_LIST` for command streaming in the DDS functionality"""
 15
 16    class WRITE_MODE(IntEnum):
 17        NO_CHECK = 0
 18        EXCEPTION_IF_FULL = 1
 19        WAIT_IF_FULL = 2
 20
 21    mode : int = WRITE_MODE.NO_CHECK
 22
 23    command_list : ctypes._Pointer = None
 24    commands_transfered : int = 0
 25    current_index : int = 0
 26    
 27    _dtm : int = SPCM_DDS_DTM_SINGLE
 28    _list_size : int = KIBI(16)
 29
 30    def __init__(self, *args, **kwargs) -> None:
 31        kwargs["no_units"] = kwargs.get("no_units", True) # disable units to make the command queu faster
 32        super().__init__(*args, **kwargs)
 33
 34        self.command_list = None
 35        self.current_index = 0
 36        
 37        self._dtm = SPCM_DDS_DTM_SINGLE
 38        
 39        self.list_size = self.default_size()
 40
 41    def data_transfer_mode(self, mode : int) -> None:
 42        """
 43        set the data transfer mode of the DDS
 44
 45        Parameters
 46        ----------
 47        mode : int
 48            the data transfer mode
 49        """
 50
 51        self._dtm = mode
 52        self.card.set_i(SPC_DDS_DATA_TRANSFER_MODE, mode)
 53        self.list_size = self.default_size()
 54    
 55    def default_size(self) -> int:
 56        """
 57        automatically determine the size of the commands list
 58        """
 59
 60        if self._dtm == SPCM_DDS_DTM_SINGLE:
 61            return self.card.get_i(SPC_DDS_QUEUE_CMD_MAX) // 2
 62        elif self._dtm == SPCM_DDS_DTM_DMA:
 63            return KIBI(16)
 64        raise SpcmException(text="Data transfer mode not supported.")
 65    
 66    def allocate(self) -> None:
 67        """
 68        allocate memory for the commands list
 69        """
 70        if self.command_list is not None:
 71            del self.command_list
 72        elems = (ST_LIST_PARAM * (self._list_size + 1))() # +1 for the write to card command at the end
 73        self.command_list = ctypes.cast(elems, ctypes.POINTER(ST_LIST_PARAM))
 74        self.current_index = 0
 75
 76    def load(self, data : dict, exec_mode : int = SPCM_DDS_CMD_EXEC_AT_TRG, repeat : int = 1) -> None:
 77        """
 78        preload the command list with data
 79
 80        Parameters
 81        ----------
 82        data : dict
 83            the data to be preloaded
 84        mode : int = SPCM_DDS_CMD_EXEC_AT_TRG
 85            the mode of execution
 86        repeat : int = 1
 87            the number of times to repeat the data, if 0 is given the buffer is filled up with the maximal number of blocks that fit in.
 88        
 89        TODO make this possible for multiple different keys
 90        """
 91
 92        key = list(data.keys())[0]
 93        value_list = data[key] # For now only take the first key
 94        size = len(value_list)
 95        index = 0
 96        if repeat == 0:
 97            # repeat the data until the block is full
 98            repeat = self.list_size // (2*size)
 99        for _ in range(repeat):
100            for value in value_list:
101                # Write value
102                self.command_list[index].lReg = key
103                self.command_list[index].lType = TYPE_DOUBLE
104                self.command_list[index].dValue = value
105                index += 1
106                # Write trigger mode
107                self.command_list[index].lReg = SPC_DDS_CMD
108                self.command_list[index].lType = TYPE_INT64
109                self.command_list[index].llValue = exec_mode
110                index += 1
111        self.current_index = index
112        self.write_to_card()
113
114    def write_to_card(self) -> None:
115        """
116        write the command list to the card
117        """
118
119        self.command_list[self.current_index].lReg = SPC_DDS_CMD
120        self.command_list[self.current_index].lType = TYPE_INT64
121        self.command_list[self.current_index].llValue = SPCM_DDS_CMD_WRITE_TO_CARD
122        self.current_index += 1
123
124    def write(self) -> None:
125        """
126        send the currently loaded data to the card
127        """
128
129        if self.mode == self.WRITE_MODE.EXCEPTION_IF_FULL:
130            if self.avail_user_len() < (self.current_index) * ctypes.sizeof(ST_LIST_PARAM):
131                raise SpcmException(text="Buffer is full")
132        elif self.mode == self.WRITE_MODE.WAIT_IF_FULL:
133            timer = 0
134            while self.avail_user_len() < (self.current_index) * ctypes.sizeof(ST_LIST_PARAM):
135                print("Waiting for buffer to empty {}".format("."*(timer//100)), end="\r")
136                timer = (timer + 1) % 400
137        self.card.set_ptr(SPC_REGISTER_LIST, self.command_list, (self.current_index) * ctypes.sizeof(ST_LIST_PARAM))
138    
139    def avail_user_len(self) -> int:
140        """
141        get the available space for commands in the hardware queue
142
143        TODO: check if this correct. Probably we should use fillsize_promille here
144        """
145
146        if self._dtm == SPCM_DDS_DTM_SINGLE:
147            return self.list_size - self.card.get_i(SPC_DDS_QUEUE_CMD_COUNT)
148        elif self._dtm == SPCM_DDS_DTM_DMA:
149            return self.card.get_i(SPC_DATA_AVAIL_USER_LEN)
150        else:
151            raise SpcmException(text="Data transfer mode not supported.")
152
153
154    @property
155    def list_size(self) -> int:
156        """
157        get the size of the command list
158        """
159
160        return self._list_size
161
162    @list_size.setter
163    def list_size(self, size : int) -> None:
164        """
165        set the size of the command list
166
167        Parameters
168        ----------
169        size : int
170            the size of the command list
171        """
172
173        self._list_size = size
174        self.allocate()
175    
176    def reset(self) -> None:
177        """
178        reset the dds firmware
179        """
180
181        # The reset shouldn't be queued!
182        self.card.set_i(SPC_DDS_CMD, SPCM_DDS_CMD_RESET)

Abstraction of the set_ptr and register SPC_REGISTER_LIST for command streaming in the DDS functionality

DDSCommandList(*args, **kwargs)
30    def __init__(self, *args, **kwargs) -> None:
31        kwargs["no_units"] = kwargs.get("no_units", True) # disable units to make the command queu faster
32        super().__init__(*args, **kwargs)
33
34        self.command_list = None
35        self.current_index = 0
36        
37        self._dtm = SPCM_DDS_DTM_SINGLE
38        
39        self.list_size = self.default_size()

Takes a Card object that is used by the functionality

Parameters
  • card (Card): a Card object on which the functionality works
mode: int = <WRITE_MODE.NO_CHECK: 0>
command_list: _ctypes._Pointer = None
commands_transfered: int = 0
current_index: int = 0
list_size: int
154    @property
155    def list_size(self) -> int:
156        """
157        get the size of the command list
158        """
159
160        return self._list_size

get the size of the command list

def data_transfer_mode(self, mode: int) -> None:
41    def data_transfer_mode(self, mode : int) -> None:
42        """
43        set the data transfer mode of the DDS
44
45        Parameters
46        ----------
47        mode : int
48            the data transfer mode
49        """
50
51        self._dtm = mode
52        self.card.set_i(SPC_DDS_DATA_TRANSFER_MODE, mode)
53        self.list_size = self.default_size()

set the data transfer mode of the DDS

Parameters
  • mode (int): the data transfer mode
def default_size(self) -> int:
55    def default_size(self) -> int:
56        """
57        automatically determine the size of the commands list
58        """
59
60        if self._dtm == SPCM_DDS_DTM_SINGLE:
61            return self.card.get_i(SPC_DDS_QUEUE_CMD_MAX) // 2
62        elif self._dtm == SPCM_DDS_DTM_DMA:
63            return KIBI(16)
64        raise SpcmException(text="Data transfer mode not supported.")

automatically determine the size of the commands list

def allocate(self) -> None:
66    def allocate(self) -> None:
67        """
68        allocate memory for the commands list
69        """
70        if self.command_list is not None:
71            del self.command_list
72        elems = (ST_LIST_PARAM * (self._list_size + 1))() # +1 for the write to card command at the end
73        self.command_list = ctypes.cast(elems, ctypes.POINTER(ST_LIST_PARAM))
74        self.current_index = 0

allocate memory for the commands list

def load(self, data: dict, exec_mode: int = 2, repeat: int = 1) -> None:
 76    def load(self, data : dict, exec_mode : int = SPCM_DDS_CMD_EXEC_AT_TRG, repeat : int = 1) -> None:
 77        """
 78        preload the command list with data
 79
 80        Parameters
 81        ----------
 82        data : dict
 83            the data to be preloaded
 84        mode : int = SPCM_DDS_CMD_EXEC_AT_TRG
 85            the mode of execution
 86        repeat : int = 1
 87            the number of times to repeat the data, if 0 is given the buffer is filled up with the maximal number of blocks that fit in.
 88        
 89        TODO make this possible for multiple different keys
 90        """
 91
 92        key = list(data.keys())[0]
 93        value_list = data[key] # For now only take the first key
 94        size = len(value_list)
 95        index = 0
 96        if repeat == 0:
 97            # repeat the data until the block is full
 98            repeat = self.list_size // (2*size)
 99        for _ in range(repeat):
100            for value in value_list:
101                # Write value
102                self.command_list[index].lReg = key
103                self.command_list[index].lType = TYPE_DOUBLE
104                self.command_list[index].dValue = value
105                index += 1
106                # Write trigger mode
107                self.command_list[index].lReg = SPC_DDS_CMD
108                self.command_list[index].lType = TYPE_INT64
109                self.command_list[index].llValue = exec_mode
110                index += 1
111        self.current_index = index
112        self.write_to_card()

preload the command list with data

Parameters
  • data (dict): the data to be preloaded
  • mode (int = SPCM_DDS_CMD_EXEC_AT_TRG): the mode of execution
  • repeat (int = 1): the number of times to repeat the data, if 0 is given the buffer is filled up with the maximal number of blocks that fit in.
  • TODO make this possible for multiple different keys
def write_to_card(self) -> None:
114    def write_to_card(self) -> None:
115        """
116        write the command list to the card
117        """
118
119        self.command_list[self.current_index].lReg = SPC_DDS_CMD
120        self.command_list[self.current_index].lType = TYPE_INT64
121        self.command_list[self.current_index].llValue = SPCM_DDS_CMD_WRITE_TO_CARD
122        self.current_index += 1

write the command list to the card

def write(self) -> None:
124    def write(self) -> None:
125        """
126        send the currently loaded data to the card
127        """
128
129        if self.mode == self.WRITE_MODE.EXCEPTION_IF_FULL:
130            if self.avail_user_len() < (self.current_index) * ctypes.sizeof(ST_LIST_PARAM):
131                raise SpcmException(text="Buffer is full")
132        elif self.mode == self.WRITE_MODE.WAIT_IF_FULL:
133            timer = 0
134            while self.avail_user_len() < (self.current_index) * ctypes.sizeof(ST_LIST_PARAM):
135                print("Waiting for buffer to empty {}".format("."*(timer//100)), end="\r")
136                timer = (timer + 1) % 400
137        self.card.set_ptr(SPC_REGISTER_LIST, self.command_list, (self.current_index) * ctypes.sizeof(ST_LIST_PARAM))

send the currently loaded data to the card

def avail_user_len(self) -> int:
139    def avail_user_len(self) -> int:
140        """
141        get the available space for commands in the hardware queue
142
143        TODO: check if this correct. Probably we should use fillsize_promille here
144        """
145
146        if self._dtm == SPCM_DDS_DTM_SINGLE:
147            return self.list_size - self.card.get_i(SPC_DDS_QUEUE_CMD_COUNT)
148        elif self._dtm == SPCM_DDS_DTM_DMA:
149            return self.card.get_i(SPC_DATA_AVAIL_USER_LEN)
150        else:
151            raise SpcmException(text="Data transfer mode not supported.")

get the available space for commands in the hardware queue

TODO: check if this correct. Probably we should use fillsize_promille here

def reset(self) -> None:
176    def reset(self) -> None:
177        """
178        reset the dds firmware
179        """
180
181        # The reset shouldn't be queued!
182        self.card.set_i(SPC_DDS_CMD, SPCM_DDS_CMD_RESET)

reset the dds firmware

class DDSCommandList.WRITE_MODE(enum.IntEnum):
16    class WRITE_MODE(IntEnum):
17        NO_CHECK = 0
18        EXCEPTION_IF_FULL = 1
19        WAIT_IF_FULL = 2

An enumeration.

NO_CHECK = <WRITE_MODE.NO_CHECK: 0>
EXCEPTION_IF_FULL = <WRITE_MODE.EXCEPTION_IF_FULL: 1>
WAIT_IF_FULL = <WRITE_MODE.WAIT_IF_FULL: 2>
class DDSCommandQueue(spcm.DDSCommandList):
  8class DDSCommandQueue(DDSCommandList):
  9    """
 10    Abstraction class of the set_ptr and register `SPC_REGISTER_LIST` for command streaming in the DDS functionality.
 11    This class is used to write commands to the card in a more efficient way using a queuing mechanism that writes
 12    to the card when the queue is filled-up.
 13    """
 14
 15    def __init__(self, *args, **kwargs) -> None:
 16        super().__init__(*args, **kwargs)
 17        self.mode = self.WRITE_MODE.NO_CHECK
 18
 19    def set_i(self, reg : int, value : int) -> None:
 20        """
 21        set an integer value to a register
 22
 23        Parameters
 24        ----------
 25        reg : int
 26            the register to be changed
 27        value : int
 28            the value to be set
 29        """
 30        
 31        self.command_list[self.current_index].lReg = reg
 32        self.command_list[self.current_index].lType = TYPE_INT64
 33        self.command_list[self.current_index].llValue = value
 34        self.current_index += 1
 35
 36        if self.current_index >= self.list_size:
 37            self.write_to_card()
 38    
 39    def set_d(self, reg : int, value) -> None:
 40        """
 41        set a double value to a register
 42
 43        Parameters
 44        ----------
 45        reg : int
 46            the register to be changed
 47        value : np._float64
 48            the value to be set
 49        """
 50        
 51        self.command_list[self.current_index].lReg = reg
 52        self.command_list[self.current_index].lType = TYPE_DOUBLE
 53        self.command_list[self.current_index].dValue = value
 54        self.current_index += 1
 55
 56        if self.current_index >= self.list_size:
 57            self.write_to_card()
 58
 59    def write_to_card(self) -> None:
 60        """
 61        write the current list of commands to the card
 62        """
 63
 64        super().write_to_card()
 65        self.write()
 66
 67    def write(self) -> None:
 68        """
 69        write the current list of commands to the card and reset the current command index
 70        """
 71
 72        super().write()
 73        self.current_index = 0
 74
 75    # # DDS "static" parameters
 76    # def amp(self, index : int, amplitude : float) -> None:
 77    #     """
 78    #     set the amplitude of the sine wave of a specific core (see register `SPC_DDS_CORE0_AMP` in the manual)
 79        
 80    #     Parameters
 81    #     ----------
 82    #     index : int
 83    #         the core index
 84    #     amplitude : float
 85    #         the value between 0 and 1 corresponding to the amplitude
 86    #     """
 87
 88    #     self.set_d(SPC_DDS_CORE0_AMP + index, amplitude)
 89
 90    # def freq(self, index : int, frequency : float) -> None:
 91    #     """
 92    #     set the frequency of the sine wave of a specific core (see register `SPC_DDS_CORE0_FREQ` in the manual)
 93        
 94    #     Parameters
 95    #     ----------
 96    #     index : int
 97    #         the core index
 98    #     frequency : float
 99    #         the value of the frequency in Hz
100    #     """
101
102    #     self.set_d(SPC_DDS_CORE0_FREQ + index, frequency)
103    
104    # def phase(self, index : int, phase : float) -> None:
105    #     """
106    #     set the phase of the sine wave of a specific core (see register `SPC_DDS_CORE0_PHASE` in the manual)
107        
108    #     Parameters
109    #     ----------
110    #     core_index : int
111    #         the index of the core to be changed
112    #     phase : float
113    #         the value between 0 and 360 degrees of the phase
114    #     """
115
116    #     self.set_d(SPC_DDS_CORE0_PHASE + index, phase)
117    
118    # def freq_slope(self, core_index : int, slope : float) -> None:
119    #     """
120    #     set the frequency slope of the linearly changing frequency of the sine wave of a specific core (see register `SPC_DDS_CORE0_FREQ_SLOPE` in the manual)
121        
122    #     Parameters
123    #     ----------
124    #     core_index : int
125    #         the index of the core to be changed
126    #     slope : float
127    #         the rate of frequency change in Hz/s
128    #     """
129
130    #     self.set_d(SPC_DDS_CORE0_FREQ_SLOPE + core_index, slope)
131
132    # def amp_slope(self, core_index : int, slope : float) -> None:
133    #     """
134    #     set the amplitude slope of the linearly changing amplitude of the sine wave of a specific core (see register `SPC_DDS_CORE0_AMP_SLOPE` in the manual)
135        
136    #     Parameters
137    #     ----------
138    #     core_index : int
139    #         the index of the core to be changed
140    #     slope : float
141    #         the rate of amplitude change in 1/s
142    #     """
143
144    #     self.set_d(SPC_DDS_CORE0_AMP_SLOPE + core_index, slope)
145
146    # def trg_timer(self, period : float) -> None:
147    #     """
148    #     set the period at which the timer should raise DDS trigger events. (see register `SPC_DDS_TRG_TIMER` in the manual)
149
150    #     NOTE
151    #     ----
152    #     only used in conjecture with the trigger source set to SPCM_DDS_TRG_SRC_TIMER ---
153
154    #     Parameters
155    #     ----------
156    #     period : float
157    #         the time between DDS trigger events in seconds
158    #     """
159
160    #     self.set_d(SPC_DDS_TRG_TIMER, float(period))

Abstraction class of the set_ptr and register SPC_REGISTER_LIST for command streaming in the DDS functionality. This class is used to write commands to the card in a more efficient way using a queuing mechanism that writes to the card when the queue is filled-up.

DDSCommandQueue(*args, **kwargs)
15    def __init__(self, *args, **kwargs) -> None:
16        super().__init__(*args, **kwargs)
17        self.mode = self.WRITE_MODE.NO_CHECK

Takes a Card object that is used by the functionality

Parameters
  • card (Card): a Card object on which the functionality works
mode = <WRITE_MODE.NO_CHECK: 0>
def set_i(self, reg: int, value: int) -> None:
19    def set_i(self, reg : int, value : int) -> None:
20        """
21        set an integer value to a register
22
23        Parameters
24        ----------
25        reg : int
26            the register to be changed
27        value : int
28            the value to be set
29        """
30        
31        self.command_list[self.current_index].lReg = reg
32        self.command_list[self.current_index].lType = TYPE_INT64
33        self.command_list[self.current_index].llValue = value
34        self.current_index += 1
35
36        if self.current_index >= self.list_size:
37            self.write_to_card()

set an integer value to a register

Parameters
  • reg (int): the register to be changed
  • value (int): the value to be set
def set_d(self, reg: int, value) -> None:
39    def set_d(self, reg : int, value) -> None:
40        """
41        set a double value to a register
42
43        Parameters
44        ----------
45        reg : int
46            the register to be changed
47        value : np._float64
48            the value to be set
49        """
50        
51        self.command_list[self.current_index].lReg = reg
52        self.command_list[self.current_index].lType = TYPE_DOUBLE
53        self.command_list[self.current_index].dValue = value
54        self.current_index += 1
55
56        if self.current_index >= self.list_size:
57            self.write_to_card()

set a double value to a register

Parameters
  • reg (int): the register to be changed
  • value (np._float64): the value to be set
def write_to_card(self) -> None:
59    def write_to_card(self) -> None:
60        """
61        write the current list of commands to the card
62        """
63
64        super().write_to_card()
65        self.write()

write the current list of commands to the card

def write(self) -> None:
67    def write(self) -> None:
68        """
69        write the current list of commands to the card and reset the current command index
70        """
71
72        super().write()
73        self.current_index = 0

write the current list of commands to the card and reset the current command index

class PulseGenerator:
 16class PulseGenerator:
 17    """
 18    a class to implement a single pulse generator
 19    
 20    Parameters
 21    ----------
 22    card : Card
 23        the card object that is used by the functionality
 24    pg_index : int
 25        the index of the used pulse generator
 26    """
 27
 28    card : Card
 29    pg_index : int
 30    """The index of the pulse generator"""
 31
 32    _reg_distance : int = 100
 33
 34    def __init__(self, card : Card, pg_index : int, *args, **kwargs) -> None:
 35        """
 36        The constructor of the PulseGenerator class
 37
 38        Parameters
 39        ----------
 40        card : Card
 41            the card object that is used by the functionality
 42        pg_index : int
 43            the index of the used pulse generator
 44        """
 45        
 46        self.card = card
 47        self.pg_index = pg_index
 48
 49    def __str__(self) -> str:
 50        """
 51        String representation of the PulseGenerator class
 52    
 53        Returns
 54        -------
 55        str
 56            String representation of the PulseGenerator class
 57        """
 58        
 59        return f"PulseGenerator(card={self.card}, pg_index={self.pg_index})"
 60    
 61    __repr__ = __str__
 62    
 63    # The trigger behavior of the pulse generator
 64    def mode(self, mode : int = None) -> int:
 65        """
 66        Set the trigger mode of the pulse generator (see register 'SPC_XIO_PULSEGEN0_MODE' in chapter `Pulse Generator` in the manual)
 67
 68        Parameters
 69        ----------
 70        mode : int
 71            The trigger mode
 72        
 73        Returns
 74        -------
 75        int
 76            The trigger mode
 77        """
 78
 79        if mode is not None:
 80            self.card.set_i(SPC_XIO_PULSEGEN0_MODE + self._reg_distance*self.pg_index, mode)
 81        return self.card.get_i(SPC_XIO_PULSEGEN0_MODE + self._reg_distance*self.pg_index)
 82    
 83    # The duration of a single period
 84    def period_length(self, length : int = None) -> int:
 85        """
 86        Set the period length of the pulse generator (see register 'SPC_XIO_PULSEGEN0_LEN' in chapter `Pulse Generator` in the manual)
 87
 88        Parameters
 89        ----------
 90        length : int
 91            The period length in clock cycles
 92        
 93        Returns
 94        -------
 95        int
 96            The period length in clock cycles
 97        """
 98
 99        if length is not None:
100            self.card.set_i(SPC_XIO_PULSEGEN0_LEN + self._reg_distance*self.pg_index, length)
101        return self.card.get_i(SPC_XIO_PULSEGEN0_LEN + self._reg_distance*self.pg_index)
102
103    def avail_length_min(self) -> int:
104        """
105        Returns the minimum length (period) of the pulse generator’s output pulses in clock cycles. (see register 'SPC_XIO_PULSEGEN_AVAILLEN_MIN' in chapter `Pulse Generator` in the manual)
106
107        Returns
108        -------
109        int
110            The available minimal length in clock cycles
111        """
112        return self.card.get_i(SPC_XIO_PULSEGEN_AVAILLEN_MIN)
113    
114    def avail_length_max(self) -> int:
115        """
116        Returns the maximum length (period) of the pulse generator’s output pulses in clock cycles. (see register 'SPC_XIO_PULSEGEN_AVAILLEN_MAX' in chapter `Pulse Generator` in the manual)
117
118        Returns
119        -------
120        int
121            The available maximal length in clock cycles
122        """
123        return self.card.get_i(SPC_XIO_PULSEGEN_AVAILLEN_MAX)
124    
125    def avail_length_step(self) -> int:
126        """
127        Returns the step size of the pulse generator’s output pulses in clock cycles. (see register 'SPC_XIO_PULSEGEN_AVAILLEN_STEP' in chapter `Pulse Generator` in the manual)
128
129        Returns
130        -------
131        int
132            The available step size in clock cycles
133        """
134        return self.card.get_i(SPC_XIO_PULSEGEN_AVAILLEN_STEP)
135    
136    # The time that the signal is high during one period
137    def high_length(self, length : int = None) -> int:
138        """
139        Set the high length of the pulse generator (see register 'SPC_XIO_PULSEGEN0_HIGH' in chapter `Pulse Generator` in the manual)
140
141        Parameters
142        ----------
143        pg_index : int
144            The index of the pulse generator
145        length : int
146            The high length in clock cycles
147        
148        Returns
149        -------
150        int
151            The high length in clock cycles
152        """
153
154        if length is not None:
155            self.card.set_i(SPC_XIO_PULSEGEN0_HIGH + self._reg_distance*self.pg_index, length)
156        return self.card.get_i(SPC_XIO_PULSEGEN0_HIGH + self._reg_distance*self.pg_index)
157    
158    def avail_high_min(self) -> int:
159        """
160        Returns the minimum high length of the pulse generator’s output pulses in clock cycles. (see register 'SPC_XIO_PULSEGEN_AVAILHIGH_MIN' in chapter `Pulse Generator` in the manual)
161
162        Returns
163        -------
164        int
165            The available minimal high length in clock cycles
166        """
167        return self.card.get_i(SPC_XIO_PULSEGEN_AVAILHIGH_MIN)
168    
169    def avail_high_max(self) -> int:
170        """
171        Returns the maximum high length of the pulse generator’s output pulses in clock cycles. (see register 'SPC_XIO_PULSEGEN_AVAILHIGH_MAX' in chapter `Pulse Generator` in the manual)
172
173        Returns
174        -------
175        int
176            The available maximal high length in clock cycles
177        """
178        return self.card.get_i(SPC_XIO_PULSEGEN_AVAILHIGH_MAX)
179    
180    def avail_high_step(self) -> int:
181        """
182        Returns the step size of the pulse generator’s output pulses in clock cycles. (see register 'SPC_XIO_PULSEGEN_AVAILHIGH_STEP' in chapter `Pulse Generator` in the manual)
183
184        Returns
185        -------
186        int
187            The available step size in clock cycles
188        """
189        return self.card.get_i(SPC_XIO_PULSEGEN_AVAILHIGH_STEP)
190    
191    # The number of times that a single period is repeated
192    def num_loops(self, loops : int = None) -> int:
193        """
194        Set the number of loops of a single period on the pulse generator (see register 'SPC_XIO_PULSEGEN0_LOOPS' in chapter `Pulse Generator` in the manual)
195
196        Parameters
197        ----------
198        loops : int
199            The number of loops
200        
201        Returns
202        -------
203        int
204            The number of loops
205        """
206
207        if loops is not None:
208            self.card.set_i(SPC_XIO_PULSEGEN0_LOOPS + self._reg_distance*self.pg_index, loops)
209        return self.card.get_i(SPC_XIO_PULSEGEN0_LOOPS + self._reg_distance*self.pg_index)
210    
211    def avail_loops_min(self) -> int:
212        """
213        Returns the minimum number of loops of the pulse generator’s output pulses. (see register 'SPC_XIO_PULSEGEN_AVAILLOOPS_MIN' in chapter `Pulse Generator` in the manual)
214
215        Returns
216        -------
217        int
218            The available minimal number of loops
219        """
220        return self.card.get_i(SPC_XIO_PULSEGEN_AVAILLOOPS_MIN)
221    
222    def avail_loops_max(self) -> int:
223        """
224        Returns the maximum number of loops of the pulse generator’s output pulses. (see register 'SPC_XIO_PULSEGEN_AVAILLOOPS_MAX' in chapter `Pulse Generator` in the manual)
225
226        Returns
227        -------
228        int
229            The available maximal number of loops
230        """
231        return self.card.get_i(SPC_XIO_PULSEGEN_AVAILLOOPS_MAX)
232    
233    def avail_loops_step(self) -> int:
234        """
235        Returns the step size of the pulse generator’s output pulses. (see register 'SPC_XIO_PULSEGEN_AVAILLOOPS_STEP' in chapter `Pulse Generator` in the manual)
236
237        Returns
238        -------
239        int
240            Returns the step size when defining the repetition of pulse generator’s output.
241        """
242        return self.card.get_i(SPC_XIO_PULSEGEN_AVAILLOOPS_STEP)
243    
244    # The delay between the start of the pulse generator and the first pulse
245    def delay(self, delay : int = None) -> int:
246        """
247        Set the delay of the pulse generator (see register 'SPC_XIO_PULSEGEN0_DELAY' in chapter `Pulse Generator` in the manual)
248
249        Parameters
250        ----------
251        delay : int
252            The delay in clock cycles
253        
254        Returns
255        -------
256        int
257            The delay in clock cycles
258        """
259
260        if delay is not None:
261            self.card.set_i(SPC_XIO_PULSEGEN0_DELAY + self._reg_distance*self.pg_index, delay)
262        return self.card.get_i(SPC_XIO_PULSEGEN0_DELAY + self._reg_distance*self.pg_index)
263    
264    def avail_delay_min(self) -> int:
265        """
266        Returns the minimum delay of the pulse generator’s output pulses in clock cycles. (see register 'SPC_XIO_PULSEGEN_AVAILDELAY_MIN' in chapter `Pulse Generator` in the manual)
267
268        Returns
269        -------
270        int
271            The available minimal delay in clock cycles
272        """
273        return self.card.get_i(SPC_XIO_PULSEGEN_AVAILDELAY_MIN)
274    
275    def avail_delay_max(self) -> int:
276        """
277        Returns the maximum delay of the pulse generator’s output pulses in clock cycles. (see register 'SPC_XIO_PULSEGEN_AVAILDELAY_MAX' in chapter `Pulse Generator` in the manual)
278
279        Returns
280        -------
281        int
282            The available maximal delay in clock cycles
283        """
284        return self.card.get_i(SPC_XIO_PULSEGEN_AVAILDELAY_MAX)
285    
286    def avail_delay_step(self) -> int:
287        """
288        Returns the step size of the delay of the pulse generator’s output pulses in clock cycles. (see register 'SPC_XIO_PULSEGEN_AVAILDELAY_STEP' in chapter `Pulse Generator` in the manual)
289
290        Returns
291        -------
292        int
293            The available step size of the delay in clock cycles
294        """
295        return self.card.get_i(SPC_XIO_PULSEGEN_AVAILDELAY_STEP)
296
297    # Trigger muxes
298    def mux1(self, mux : int = None) -> int:
299        """
300        Set the trigger mux 1 of the pulse generator (see register 'SPC_XIO_PULSEGEN0_MUX1_SRC' in chapter `Pulse Generator` in the manual)
301
302        Parameters
303        ----------
304        mux : int
305            The trigger mux 1
306        
307        Returns
308        -------
309        int
310            The trigger mux 1
311        """
312
313        if mux is not None:
314            self.card.set_i(SPC_XIO_PULSEGEN0_MUX1_SRC + self._reg_distance*self.pg_index, mux)
315        return self.card.get_i(SPC_XIO_PULSEGEN0_MUX1_SRC + self._reg_distance*self.pg_index)
316    
317    def mux2(self, mux : int = None) -> int:
318        """
319        Set the trigger mux 2 of the pulse generator (see register 'SPC_XIO_PULSEGEN0_MUX2_SRC' in chapter `Pulse Generator` in the manual)
320
321        Parameters
322        ----------
323        mux : int
324            The trigger mux 2
325
326        Returns
327        -------
328        int
329            The trigger mux 2
330        """
331
332        if mux is not None:
333            self.card.set_i(SPC_XIO_PULSEGEN0_MUX2_SRC + self._reg_distance*self.pg_index, mux)
334        return self.card.get_i(SPC_XIO_PULSEGEN0_MUX2_SRC + self._reg_distance*self.pg_index)
335    
336    def config(self, config : int = None) -> int:
337        """
338        Set the configuration of the pulse generator (see register 'SPC_XIO_PULSEGEN0_CONFIG' in chapter `Pulse Generator` in the manual)
339
340        Parameters
341        ----------
342        config : int
343            The configuration of the pulse generator
344
345        Returns
346        -------
347        int
348            The configuration of the pulse generator
349        """
350
351        if config is not None:
352            self.card.set_i(SPC_XIO_PULSEGEN0_CONFIG + self._reg_distance*self.pg_index, config)
353        return self.card.get_i(SPC_XIO_PULSEGEN0_CONFIG + self._reg_distance*self.pg_index)
354    
355    def _get_clock(self, return_unit : pint.Unit = None) -> int:
356        """
357        Get the clock rate of the pulse generator (see register 'SPC_XIO_PULSEGEN_CLOCK' in chapter `Pulse Generator` in the manual)
358
359        Returns
360        -------
361        int
362            The clock rate in Hz
363        """
364
365        return_value = self.card.get_i(SPC_XIO_PULSEGEN_CLOCK)
366        return_value = UnitConversion.to_unit(return_value * units.Hz, return_unit)
367        return return_value
368    
369    # Higher abtraction functions
370
371    def pulse_period(self, period : pint.Quantity = None, return_unit : pint.Unit = units.s) -> pint.Quantity:
372        """
373        Set the period length of the pulse generator signal in a time unit
374
375        Parameters
376        ----------
377        period : pint.Quantity
378            The period length in seconds
379        
380        Returns
381        -------
382        pint.Quantity
383            The period length in seconds
384        """
385
386        if period is not None:
387            if isinstance(period, pint.Quantity):
388                period = int((period * self._get_clock(units.Hz)).to_base_units().magnitude)
389            else:
390                raise ValueError("The period must be a pint.Quantity")
391            self.period_length(period)
392        return_value = self.period_length()
393        return_value = UnitConversion.to_unit((return_value / self._get_clock(units.Hz)), return_unit)
394        return return_value
395    
396    def repetition_rate(self, rate : pint.Quantity = None, return_unit : pint.Unit = units.Hz) -> pint.Quantity:
397        """
398        Set the repetition rate of the pulse generator signal in a frequency unit
399
400        Parameters
401        ----------
402        rate : pint.Quantity
403            The repetition rate in Hz
404        
405        Returns
406        -------
407        pint.Quantity
408            The repetition rate in Hz
409        """
410
411        if rate is not None:
412            if isinstance(rate, pint.Quantity):
413                period = int(np.rint((self._get_clock(units.Hz) / rate).to_base_units().magnitude))
414            else:
415                raise ValueError("The rate must be a pint.Quantity")
416            self.period_length(period)
417        return_value = self.period_length()
418        return_value = UnitConversion.to_unit((self._get_clock(units.Hz) / return_value), return_unit)
419        return return_value
420    
421    def pulse_length(self, length : pint.Quantity, return_unit : pint.Unit = units.s) -> pint.Quantity:
422        """
423        Set the pulse length of the pulse generator signal in a time unit
424
425        Parameters
426        ----------
427        length : pint.Quantity
428            The pulse length in seconds
429        
430        Returns
431        -------
432        pint.Quantity
433            The pulse length in seconds
434        """
435
436        if length is not None:
437            if isinstance(length, pint.Quantity):
438                length = int((length * self._get_clock(units.Hz)).to_base_units().magnitude)
439            else:
440                raise ValueError("The length must be a pint.Quantity")
441            self.high_length(length)
442        return_value = self.high_length()
443        return_value = UnitConversion.to_unit((return_value / self._get_clock(units.Hz)), return_unit)
444        return return_value
445    
446    def duty_cycle(self, duty_cycle : pint.Quantity = None, return_unit : pint.Unit = units.percent) -> pint.Quantity:
447        """
448        Set the duty cycle of the pulse generator signal in a percentage unit
449
450        Parameters
451        ----------
452        duty_cycle : pint.Quantity
453            The duty cycle in percentage
454        
455        Returns
456        -------
457        pint.Quantity
458            The duty cycle in percentage
459        """
460
461        period_length = self.period_length()
462        if duty_cycle is not None:
463            if isinstance(duty_cycle, pint.Quantity):
464                high_length = int(np.rint(period_length * duty_cycle))
465            else:
466                raise ValueError("The cycle must be a pint.Quantity")
467            self.high_length(high_length)
468        return_value = self.high_length()
469        return_value = UnitConversion.to_unit((return_value / period_length) * 100 * units.percent, return_unit)
470        return return_value
471    
472    def start_delay(self, delay : pint.Unit = None, return_unit : pint.Unit = units.s) -> pint.Unit:
473        """
474        Set the start delay of the pulse generator signal in a time unit
475
476        Parameters
477        ----------
478        delay : pint.Unit
479            The start delay in a pint quantity with time unit
480        
481        Returns
482        -------
483        pint.Unit
484            The start delay in a pint quantity with time unit
485        """
486
487        if delay is not None:
488            if isinstance(delay, pint.Quantity):
489                delay = int((delay * self._get_clock(units.Hz)).to_base_units().magnitude)
490            else:
491                raise ValueError("The delay must be a pint.Quantity")
492        return_value = self.delay(delay)
493        return_value = UnitConversion.to_unit((return_value / self._get_clock(units.Hz)), return_unit)
494        return return_value
495    
496    repetitions = num_loops
497
498    def start_condition_state_signal(self, signal : int = 0, invert : bool = False) -> int:
499        """
500        Set the start condition state signal of the pulse generator (see register 'SPC_XIO_PULSEGEN0_MUX1' in chapter `Pulse Generator` in the manual)
501
502        NOTE
503        ----
504        The Pulse Generator is started when the combined signal of both start condition signals are true and a rising edge
505        is detected. The invert parameter inverts the start condition state signal.
506        
507        Parameters
508        ----------
509        signal : int
510            The start condition state signal
511        invert : bool
512            Invert the start condition state signal
513        
514        Returns
515        -------
516        int
517            The start condition state signal
518        """
519
520        return_signal = self.mux1(signal)
521        return_invert = self.config()
522        if invert:
523            return_invert |= SPCM_PULSEGEN_CONFIG_MUX1_INVERT
524        else:
525            return_invert &= ~SPCM_PULSEGEN_CONFIG_MUX1_INVERT
526        return_invert = self.config(return_invert)
527        return return_signal, ((return_invert & SPCM_PULSEGEN_CONFIG_MUX1_INVERT) != 0)
528
529    def start_condition_trigger_signal(self, signal : int = 0, invert : bool = False) -> int:
530        """
531        Set the start condition trigger signal of the pulse generator (see register 'SPC_XIO_PULSEGEN0_MUX2' in chapter `Pulse Generator` in the manual)
532
533        NOTE
534        ----
535        The Pulse Generator is started when the combined signal of both start condition signals are true and a rising edge
536        is detected. The invert parameter inverts the start condition state signal.
537        
538        Parameters
539        ----------
540        signal : int
541            The start condition trigger signal
542        invert : bool
543            Invert the start condition trigger signal
544        
545        Returns
546        -------
547        int
548            The start condition trigger signal
549        """
550
551        return_signal = self.mux2(signal)
552        return_invert = self.config()
553        if invert:
554            return_invert |= SPCM_PULSEGEN_CONFIG_MUX2_INVERT
555        else:
556            return_invert &= ~SPCM_PULSEGEN_CONFIG_MUX2_INVERT
557        return_invert = self.config(return_invert)
558        return return_signal, ((return_invert & SPCM_PULSEGEN_CONFIG_MUX2_INVERT) != 0)
559    
560    def invert_start_condition(self, invert : bool = None) -> bool:
561        """
562        Invert the start condition of the pulse generator
563
564        Parameters
565        ----------
566        invert : bool
567            Invert the start condition
568        
569        Returns
570        -------
571        bool
572            The start condition inversion
573        """
574
575        if invert is not None:
576            return_invert = self.config()
577            if invert:
578                return_invert |= SPCM_PULSEGEN_CONFIG_INVERT
579            else:
580                return_invert &= ~SPCM_PULSEGEN_CONFIG_INVERT
581            self.config(return_invert)
582        return ((self.config() & SPCM_PULSEGEN_CONFIG_INVERT) != 0)

a class to implement a single pulse generator

Parameters
  • card (Card): the card object that is used by the functionality
  • pg_index (int): the index of the used pulse generator
PulseGenerator(card: Card, pg_index: int, *args, **kwargs)
34    def __init__(self, card : Card, pg_index : int, *args, **kwargs) -> None:
35        """
36        The constructor of the PulseGenerator class
37
38        Parameters
39        ----------
40        card : Card
41            the card object that is used by the functionality
42        pg_index : int
43            the index of the used pulse generator
44        """
45        
46        self.card = card
47        self.pg_index = pg_index

The constructor of the PulseGenerator class

Parameters
  • card (Card): the card object that is used by the functionality
  • pg_index (int): the index of the used pulse generator
card: Card
pg_index: int

The index of the pulse generator

def mode(self, mode: int = None) -> int:
64    def mode(self, mode : int = None) -> int:
65        """
66        Set the trigger mode of the pulse generator (see register 'SPC_XIO_PULSEGEN0_MODE' in chapter `Pulse Generator` in the manual)
67
68        Parameters
69        ----------
70        mode : int
71            The trigger mode
72        
73        Returns
74        -------
75        int
76            The trigger mode
77        """
78
79        if mode is not None:
80            self.card.set_i(SPC_XIO_PULSEGEN0_MODE + self._reg_distance*self.pg_index, mode)
81        return self.card.get_i(SPC_XIO_PULSEGEN0_MODE + self._reg_distance*self.pg_index)

Set the trigger mode of the pulse generator (see register 'SPC_XIO_PULSEGEN0_MODE' in chapter Pulse Generator in the manual)

Parameters
  • mode (int): The trigger mode
Returns
  • int: The trigger mode
def period_length(self, length: int = None) -> int:
 84    def period_length(self, length : int = None) -> int:
 85        """
 86        Set the period length of the pulse generator (see register 'SPC_XIO_PULSEGEN0_LEN' in chapter `Pulse Generator` in the manual)
 87
 88        Parameters
 89        ----------
 90        length : int
 91            The period length in clock cycles
 92        
 93        Returns
 94        -------
 95        int
 96            The period length in clock cycles
 97        """
 98
 99        if length is not None:
100            self.card.set_i(SPC_XIO_PULSEGEN0_LEN + self._reg_distance*self.pg_index, length)
101        return self.card.get_i(SPC_XIO_PULSEGEN0_LEN + self._reg_distance*self.pg_index)

Set the period length of the pulse generator (see register 'SPC_XIO_PULSEGEN0_LEN' in chapter Pulse Generator in the manual)

Parameters
  • length (int): The period length in clock cycles
Returns
  • int: The period length in clock cycles
def avail_length_min(self) -> int:
103    def avail_length_min(self) -> int:
104        """
105        Returns the minimum length (period) of the pulse generator’s output pulses in clock cycles. (see register 'SPC_XIO_PULSEGEN_AVAILLEN_MIN' in chapter `Pulse Generator` in the manual)
106
107        Returns
108        -------
109        int
110            The available minimal length in clock cycles
111        """
112        return self.card.get_i(SPC_XIO_PULSEGEN_AVAILLEN_MIN)

Returns the minimum length (period) of the pulse generator’s output pulses in clock cycles. (see register 'SPC_XIO_PULSEGEN_AVAILLEN_MIN' in chapter Pulse Generator in the manual)

Returns
  • int: The available minimal length in clock cycles
def avail_length_max(self) -> int:
114    def avail_length_max(self) -> int:
115        """
116        Returns the maximum length (period) of the pulse generator’s output pulses in clock cycles. (see register 'SPC_XIO_PULSEGEN_AVAILLEN_MAX' in chapter `Pulse Generator` in the manual)
117
118        Returns
119        -------
120        int
121            The available maximal length in clock cycles
122        """
123        return self.card.get_i(SPC_XIO_PULSEGEN_AVAILLEN_MAX)

Returns the maximum length (period) of the pulse generator’s output pulses in clock cycles. (see register 'SPC_XIO_PULSEGEN_AVAILLEN_MAX' in chapter Pulse Generator in the manual)

Returns
  • int: The available maximal length in clock cycles
def avail_length_step(self) -> int:
125    def avail_length_step(self) -> int:
126        """
127        Returns the step size of the pulse generator’s output pulses in clock cycles. (see register 'SPC_XIO_PULSEGEN_AVAILLEN_STEP' in chapter `Pulse Generator` in the manual)
128
129        Returns
130        -------
131        int
132            The available step size in clock cycles
133        """
134        return self.card.get_i(SPC_XIO_PULSEGEN_AVAILLEN_STEP)

Returns the step size of the pulse generator’s output pulses in clock cycles. (see register 'SPC_XIO_PULSEGEN_AVAILLEN_STEP' in chapter Pulse Generator in the manual)

Returns
  • int: The available step size in clock cycles
def high_length(self, length: int = None) -> int:
137    def high_length(self, length : int = None) -> int:
138        """
139        Set the high length of the pulse generator (see register 'SPC_XIO_PULSEGEN0_HIGH' in chapter `Pulse Generator` in the manual)
140
141        Parameters
142        ----------
143        pg_index : int
144            The index of the pulse generator
145        length : int
146            The high length in clock cycles
147        
148        Returns
149        -------
150        int
151            The high length in clock cycles
152        """
153
154        if length is not None:
155            self.card.set_i(SPC_XIO_PULSEGEN0_HIGH + self._reg_distance*self.pg_index, length)
156        return self.card.get_i(SPC_XIO_PULSEGEN0_HIGH + self._reg_distance*self.pg_index)

Set the high length of the pulse generator (see register 'SPC_XIO_PULSEGEN0_HIGH' in chapter Pulse Generator in the manual)

Parameters
  • pg_index (int): The index of the pulse generator
  • length (int): The high length in clock cycles
Returns
  • int: The high length in clock cycles
def avail_high_min(self) -> int:
158    def avail_high_min(self) -> int:
159        """
160        Returns the minimum high length of the pulse generator’s output pulses in clock cycles. (see register 'SPC_XIO_PULSEGEN_AVAILHIGH_MIN' in chapter `Pulse Generator` in the manual)
161
162        Returns
163        -------
164        int
165            The available minimal high length in clock cycles
166        """
167        return self.card.get_i(SPC_XIO_PULSEGEN_AVAILHIGH_MIN)

Returns the minimum high length of the pulse generator’s output pulses in clock cycles. (see register 'SPC_XIO_PULSEGEN_AVAILHIGH_MIN' in chapter Pulse Generator in the manual)

Returns
  • int: The available minimal high length in clock cycles
def avail_high_max(self) -> int:
169    def avail_high_max(self) -> int:
170        """
171        Returns the maximum high length of the pulse generator’s output pulses in clock cycles. (see register 'SPC_XIO_PULSEGEN_AVAILHIGH_MAX' in chapter `Pulse Generator` in the manual)
172
173        Returns
174        -------
175        int
176            The available maximal high length in clock cycles
177        """
178        return self.card.get_i(SPC_XIO_PULSEGEN_AVAILHIGH_MAX)

Returns the maximum high length of the pulse generator’s output pulses in clock cycles. (see register 'SPC_XIO_PULSEGEN_AVAILHIGH_MAX' in chapter Pulse Generator in the manual)

Returns
  • int: The available maximal high length in clock cycles
def avail_high_step(self) -> int:
180    def avail_high_step(self) -> int:
181        """
182        Returns the step size of the pulse generator’s output pulses in clock cycles. (see register 'SPC_XIO_PULSEGEN_AVAILHIGH_STEP' in chapter `Pulse Generator` in the manual)
183
184        Returns
185        -------
186        int
187            The available step size in clock cycles
188        """
189        return self.card.get_i(SPC_XIO_PULSEGEN_AVAILHIGH_STEP)

Returns the step size of the pulse generator’s output pulses in clock cycles. (see register 'SPC_XIO_PULSEGEN_AVAILHIGH_STEP' in chapter Pulse Generator in the manual)

Returns
  • int: The available step size in clock cycles
def num_loops(self, loops: int = None) -> int:
192    def num_loops(self, loops : int = None) -> int:
193        """
194        Set the number of loops of a single period on the pulse generator (see register 'SPC_XIO_PULSEGEN0_LOOPS' in chapter `Pulse Generator` in the manual)
195
196        Parameters
197        ----------
198        loops : int
199            The number of loops
200        
201        Returns
202        -------
203        int
204            The number of loops
205        """
206
207        if loops is not None:
208            self.card.set_i(SPC_XIO_PULSEGEN0_LOOPS + self._reg_distance*self.pg_index, loops)
209        return self.card.get_i(SPC_XIO_PULSEGEN0_LOOPS + self._reg_distance*self.pg_index)

Set the number of loops of a single period on the pulse generator (see register 'SPC_XIO_PULSEGEN0_LOOPS' in chapter Pulse Generator in the manual)

Parameters
  • loops (int): The number of loops
Returns
  • int: The number of loops
def avail_loops_min(self) -> int:
211    def avail_loops_min(self) -> int:
212        """
213        Returns the minimum number of loops of the pulse generator’s output pulses. (see register 'SPC_XIO_PULSEGEN_AVAILLOOPS_MIN' in chapter `Pulse Generator` in the manual)
214
215        Returns
216        -------
217        int
218            The available minimal number of loops
219        """
220        return self.card.get_i(SPC_XIO_PULSEGEN_AVAILLOOPS_MIN)

Returns the minimum number of loops of the pulse generator’s output pulses. (see register 'SPC_XIO_PULSEGEN_AVAILLOOPS_MIN' in chapter Pulse Generator in the manual)

Returns
  • int: The available minimal number of loops
def avail_loops_max(self) -> int:
222    def avail_loops_max(self) -> int:
223        """
224        Returns the maximum number of loops of the pulse generator’s output pulses. (see register 'SPC_XIO_PULSEGEN_AVAILLOOPS_MAX' in chapter `Pulse Generator` in the manual)
225
226        Returns
227        -------
228        int
229            The available maximal number of loops
230        """
231        return self.card.get_i(SPC_XIO_PULSEGEN_AVAILLOOPS_MAX)

Returns the maximum number of loops of the pulse generator’s output pulses. (see register 'SPC_XIO_PULSEGEN_AVAILLOOPS_MAX' in chapter Pulse Generator in the manual)

Returns
  • int: The available maximal number of loops
def avail_loops_step(self) -> int:
233    def avail_loops_step(self) -> int:
234        """
235        Returns the step size of the pulse generator’s output pulses. (see register 'SPC_XIO_PULSEGEN_AVAILLOOPS_STEP' in chapter `Pulse Generator` in the manual)
236
237        Returns
238        -------
239        int
240            Returns the step size when defining the repetition of pulse generator’s output.
241        """
242        return self.card.get_i(SPC_XIO_PULSEGEN_AVAILLOOPS_STEP)

Returns the step size of the pulse generator’s output pulses. (see register 'SPC_XIO_PULSEGEN_AVAILLOOPS_STEP' in chapter Pulse Generator in the manual)

Returns
  • int: Returns the step size when defining the repetition of pulse generator’s output.
def delay(self, delay: int = None) -> int:
245    def delay(self, delay : int = None) -> int:
246        """
247        Set the delay of the pulse generator (see register 'SPC_XIO_PULSEGEN0_DELAY' in chapter `Pulse Generator` in the manual)
248
249        Parameters
250        ----------
251        delay : int
252            The delay in clock cycles
253        
254        Returns
255        -------
256        int
257            The delay in clock cycles
258        """
259
260        if delay is not None:
261            self.card.set_i(SPC_XIO_PULSEGEN0_DELAY + self._reg_distance*self.pg_index, delay)
262        return self.card.get_i(SPC_XIO_PULSEGEN0_DELAY + self._reg_distance*self.pg_index)

Set the delay of the pulse generator (see register 'SPC_XIO_PULSEGEN0_DELAY' in chapter Pulse Generator in the manual)

Parameters
  • delay (int): The delay in clock cycles
Returns
  • int: The delay in clock cycles
def avail_delay_min(self) -> int:
264    def avail_delay_min(self) -> int:
265        """
266        Returns the minimum delay of the pulse generator’s output pulses in clock cycles. (see register 'SPC_XIO_PULSEGEN_AVAILDELAY_MIN' in chapter `Pulse Generator` in the manual)
267
268        Returns
269        -------
270        int
271            The available minimal delay in clock cycles
272        """
273        return self.card.get_i(SPC_XIO_PULSEGEN_AVAILDELAY_MIN)

Returns the minimum delay of the pulse generator’s output pulses in clock cycles. (see register 'SPC_XIO_PULSEGEN_AVAILDELAY_MIN' in chapter Pulse Generator in the manual)

Returns
  • int: The available minimal delay in clock cycles
def avail_delay_max(self) -> int:
275    def avail_delay_max(self) -> int:
276        """
277        Returns the maximum delay of the pulse generator’s output pulses in clock cycles. (see register 'SPC_XIO_PULSEGEN_AVAILDELAY_MAX' in chapter `Pulse Generator` in the manual)
278
279        Returns
280        -------
281        int
282            The available maximal delay in clock cycles
283        """
284        return self.card.get_i(SPC_XIO_PULSEGEN_AVAILDELAY_MAX)

Returns the maximum delay of the pulse generator’s output pulses in clock cycles. (see register 'SPC_XIO_PULSEGEN_AVAILDELAY_MAX' in chapter Pulse Generator in the manual)

Returns
  • int: The available maximal delay in clock cycles
def avail_delay_step(self) -> int:
286    def avail_delay_step(self) -> int:
287        """
288        Returns the step size of the delay of the pulse generator’s output pulses in clock cycles. (see register 'SPC_XIO_PULSEGEN_AVAILDELAY_STEP' in chapter `Pulse Generator` in the manual)
289
290        Returns
291        -------
292        int
293            The available step size of the delay in clock cycles
294        """
295        return self.card.get_i(SPC_XIO_PULSEGEN_AVAILDELAY_STEP)

Returns the step size of the delay of the pulse generator’s output pulses in clock cycles. (see register 'SPC_XIO_PULSEGEN_AVAILDELAY_STEP' in chapter Pulse Generator in the manual)

Returns
  • int: The available step size of the delay in clock cycles
def mux1(self, mux: int = None) -> int:
298    def mux1(self, mux : int = None) -> int:
299        """
300        Set the trigger mux 1 of the pulse generator (see register 'SPC_XIO_PULSEGEN0_MUX1_SRC' in chapter `Pulse Generator` in the manual)
301
302        Parameters
303        ----------
304        mux : int
305            The trigger mux 1
306        
307        Returns
308        -------
309        int
310            The trigger mux 1
311        """
312
313        if mux is not None:
314            self.card.set_i(SPC_XIO_PULSEGEN0_MUX1_SRC + self._reg_distance*self.pg_index, mux)
315        return self.card.get_i(SPC_XIO_PULSEGEN0_MUX1_SRC + self._reg_distance*self.pg_index)

Set the trigger mux 1 of the pulse generator (see register 'SPC_XIO_PULSEGEN0_MUX1_SRC' in chapter Pulse Generator in the manual)

Parameters
  • mux (int): The trigger mux 1
Returns
  • int: The trigger mux 1
def mux2(self, mux: int = None) -> int:
317    def mux2(self, mux : int = None) -> int:
318        """
319        Set the trigger mux 2 of the pulse generator (see register 'SPC_XIO_PULSEGEN0_MUX2_SRC' in chapter `Pulse Generator` in the manual)
320
321        Parameters
322        ----------
323        mux : int
324            The trigger mux 2
325
326        Returns
327        -------
328        int
329            The trigger mux 2
330        """
331
332        if mux is not None:
333            self.card.set_i(SPC_XIO_PULSEGEN0_MUX2_SRC + self._reg_distance*self.pg_index, mux)
334        return self.card.get_i(SPC_XIO_PULSEGEN0_MUX2_SRC + self._reg_distance*self.pg_index)

Set the trigger mux 2 of the pulse generator (see register 'SPC_XIO_PULSEGEN0_MUX2_SRC' in chapter Pulse Generator in the manual)

Parameters
  • mux (int): The trigger mux 2
Returns
  • int: The trigger mux 2
def config(self, config: int = None) -> int:
336    def config(self, config : int = None) -> int:
337        """
338        Set the configuration of the pulse generator (see register 'SPC_XIO_PULSEGEN0_CONFIG' in chapter `Pulse Generator` in the manual)
339
340        Parameters
341        ----------
342        config : int
343            The configuration of the pulse generator
344
345        Returns
346        -------
347        int
348            The configuration of the pulse generator
349        """
350
351        if config is not None:
352            self.card.set_i(SPC_XIO_PULSEGEN0_CONFIG + self._reg_distance*self.pg_index, config)
353        return self.card.get_i(SPC_XIO_PULSEGEN0_CONFIG + self._reg_distance*self.pg_index)

Set the configuration of the pulse generator (see register 'SPC_XIO_PULSEGEN0_CONFIG' in chapter Pulse Generator in the manual)

Parameters
  • config (int): The configuration of the pulse generator
Returns
  • int: The configuration of the pulse generator
def pulse_period( self, period: pint.registry.Quantity = None, return_unit: pint.registry.Unit = <Unit('second')>) -> pint.registry.Quantity:
371    def pulse_period(self, period : pint.Quantity = None, return_unit : pint.Unit = units.s) -> pint.Quantity:
372        """
373        Set the period length of the pulse generator signal in a time unit
374
375        Parameters
376        ----------
377        period : pint.Quantity
378            The period length in seconds
379        
380        Returns
381        -------
382        pint.Quantity
383            The period length in seconds
384        """
385
386        if period is not None:
387            if isinstance(period, pint.Quantity):
388                period = int((period * self._get_clock(units.Hz)).to_base_units().magnitude)
389            else:
390                raise ValueError("The period must be a pint.Quantity")
391            self.period_length(period)
392        return_value = self.period_length()
393        return_value = UnitConversion.to_unit((return_value / self._get_clock(units.Hz)), return_unit)
394        return return_value

Set the period length of the pulse generator signal in a time unit

Parameters
  • period (pint.Quantity): The period length in seconds
Returns
  • pint.Quantity: The period length in seconds
def repetition_rate( self, rate: pint.registry.Quantity = None, return_unit: pint.registry.Unit = <Unit('hertz')>) -> pint.registry.Quantity:
396    def repetition_rate(self, rate : pint.Quantity = None, return_unit : pint.Unit = units.Hz) -> pint.Quantity:
397        """
398        Set the repetition rate of the pulse generator signal in a frequency unit
399
400        Parameters
401        ----------
402        rate : pint.Quantity
403            The repetition rate in Hz
404        
405        Returns
406        -------
407        pint.Quantity
408            The repetition rate in Hz
409        """
410
411        if rate is not None:
412            if isinstance(rate, pint.Quantity):
413                period = int(np.rint((self._get_clock(units.Hz) / rate).to_base_units().magnitude))
414            else:
415                raise ValueError("The rate must be a pint.Quantity")
416            self.period_length(period)
417        return_value = self.period_length()
418        return_value = UnitConversion.to_unit((self._get_clock(units.Hz) / return_value), return_unit)
419        return return_value

Set the repetition rate of the pulse generator signal in a frequency unit

Parameters
  • rate (pint.Quantity): The repetition rate in Hz
Returns
  • pint.Quantity: The repetition rate in Hz
def pulse_length( self, length: pint.registry.Quantity, return_unit: pint.registry.Unit = <Unit('second')>) -> pint.registry.Quantity:
421    def pulse_length(self, length : pint.Quantity, return_unit : pint.Unit = units.s) -> pint.Quantity:
422        """
423        Set the pulse length of the pulse generator signal in a time unit
424
425        Parameters
426        ----------
427        length : pint.Quantity
428            The pulse length in seconds
429        
430        Returns
431        -------
432        pint.Quantity
433            The pulse length in seconds
434        """
435
436        if length is not None:
437            if isinstance(length, pint.Quantity):
438                length = int((length * self._get_clock(units.Hz)).to_base_units().magnitude)
439            else:
440                raise ValueError("The length must be a pint.Quantity")
441            self.high_length(length)
442        return_value = self.high_length()
443        return_value = UnitConversion.to_unit((return_value / self._get_clock(units.Hz)), return_unit)
444        return return_value

Set the pulse length of the pulse generator signal in a time unit

Parameters
  • length (pint.Quantity): The pulse length in seconds
Returns
  • pint.Quantity: The pulse length in seconds
def duty_cycle( self, duty_cycle: pint.registry.Quantity = None, return_unit: pint.registry.Unit = <Unit('percent')>) -> pint.registry.Quantity:
446    def duty_cycle(self, duty_cycle : pint.Quantity = None, return_unit : pint.Unit = units.percent) -> pint.Quantity:
447        """
448        Set the duty cycle of the pulse generator signal in a percentage unit
449
450        Parameters
451        ----------
452        duty_cycle : pint.Quantity
453            The duty cycle in percentage
454        
455        Returns
456        -------
457        pint.Quantity
458            The duty cycle in percentage
459        """
460
461        period_length = self.period_length()
462        if duty_cycle is not None:
463            if isinstance(duty_cycle, pint.Quantity):
464                high_length = int(np.rint(period_length * duty_cycle))
465            else:
466                raise ValueError("The cycle must be a pint.Quantity")
467            self.high_length(high_length)
468        return_value = self.high_length()
469        return_value = UnitConversion.to_unit((return_value / period_length) * 100 * units.percent, return_unit)
470        return return_value

Set the duty cycle of the pulse generator signal in a percentage unit

Parameters
  • duty_cycle (pint.Quantity): The duty cycle in percentage
Returns
  • pint.Quantity: The duty cycle in percentage
def start_delay( self, delay: pint.registry.Unit = None, return_unit: pint.registry.Unit = <Unit('second')>) -> pint.registry.Unit:
472    def start_delay(self, delay : pint.Unit = None, return_unit : pint.Unit = units.s) -> pint.Unit:
473        """
474        Set the start delay of the pulse generator signal in a time unit
475
476        Parameters
477        ----------
478        delay : pint.Unit
479            The start delay in a pint quantity with time unit
480        
481        Returns
482        -------
483        pint.Unit
484            The start delay in a pint quantity with time unit
485        """
486
487        if delay is not None:
488            if isinstance(delay, pint.Quantity):
489                delay = int((delay * self._get_clock(units.Hz)).to_base_units().magnitude)
490            else:
491                raise ValueError("The delay must be a pint.Quantity")
492        return_value = self.delay(delay)
493        return_value = UnitConversion.to_unit((return_value / self._get_clock(units.Hz)), return_unit)
494        return return_value

Set the start delay of the pulse generator signal in a time unit

Parameters
  • delay (pint.Unit): The start delay in a pint quantity with time unit
Returns
  • pint.Unit: The start delay in a pint quantity with time unit
def repetitions(self, loops: int = None) -> int:
192    def num_loops(self, loops : int = None) -> int:
193        """
194        Set the number of loops of a single period on the pulse generator (see register 'SPC_XIO_PULSEGEN0_LOOPS' in chapter `Pulse Generator` in the manual)
195
196        Parameters
197        ----------
198        loops : int
199            The number of loops
200        
201        Returns
202        -------
203        int
204            The number of loops
205        """
206
207        if loops is not None:
208            self.card.set_i(SPC_XIO_PULSEGEN0_LOOPS + self._reg_distance*self.pg_index, loops)
209        return self.card.get_i(SPC_XIO_PULSEGEN0_LOOPS + self._reg_distance*self.pg_index)

Set the number of loops of a single period on the pulse generator (see register 'SPC_XIO_PULSEGEN0_LOOPS' in chapter Pulse Generator in the manual)

Parameters
  • loops (int): The number of loops
Returns
  • int: The number of loops
def start_condition_state_signal(self, signal: int = 0, invert: bool = False) -> int:
498    def start_condition_state_signal(self, signal : int = 0, invert : bool = False) -> int:
499        """
500        Set the start condition state signal of the pulse generator (see register 'SPC_XIO_PULSEGEN0_MUX1' in chapter `Pulse Generator` in the manual)
501
502        NOTE
503        ----
504        The Pulse Generator is started when the combined signal of both start condition signals are true and a rising edge
505        is detected. The invert parameter inverts the start condition state signal.
506        
507        Parameters
508        ----------
509        signal : int
510            The start condition state signal
511        invert : bool
512            Invert the start condition state signal
513        
514        Returns
515        -------
516        int
517            The start condition state signal
518        """
519
520        return_signal = self.mux1(signal)
521        return_invert = self.config()
522        if invert:
523            return_invert |= SPCM_PULSEGEN_CONFIG_MUX1_INVERT
524        else:
525            return_invert &= ~SPCM_PULSEGEN_CONFIG_MUX1_INVERT
526        return_invert = self.config(return_invert)
527        return return_signal, ((return_invert & SPCM_PULSEGEN_CONFIG_MUX1_INVERT) != 0)

Set the start condition state signal of the pulse generator (see register 'SPC_XIO_PULSEGEN0_MUX1' in chapter Pulse Generator in the manual)

NOTE

The Pulse Generator is started when the combined signal of both start condition signals are true and a rising edge is detected. The invert parameter inverts the start condition state signal.

Parameters
  • signal (int): The start condition state signal
  • invert (bool): Invert the start condition state signal
Returns
  • int: The start condition state signal
def start_condition_trigger_signal(self, signal: int = 0, invert: bool = False) -> int:
529    def start_condition_trigger_signal(self, signal : int = 0, invert : bool = False) -> int:
530        """
531        Set the start condition trigger signal of the pulse generator (see register 'SPC_XIO_PULSEGEN0_MUX2' in chapter `Pulse Generator` in the manual)
532
533        NOTE
534        ----
535        The Pulse Generator is started when the combined signal of both start condition signals are true and a rising edge
536        is detected. The invert parameter inverts the start condition state signal.
537        
538        Parameters
539        ----------
540        signal : int
541            The start condition trigger signal
542        invert : bool
543            Invert the start condition trigger signal
544        
545        Returns
546        -------
547        int
548            The start condition trigger signal
549        """
550
551        return_signal = self.mux2(signal)
552        return_invert = self.config()
553        if invert:
554            return_invert |= SPCM_PULSEGEN_CONFIG_MUX2_INVERT
555        else:
556            return_invert &= ~SPCM_PULSEGEN_CONFIG_MUX2_INVERT
557        return_invert = self.config(return_invert)
558        return return_signal, ((return_invert & SPCM_PULSEGEN_CONFIG_MUX2_INVERT) != 0)

Set the start condition trigger signal of the pulse generator (see register 'SPC_XIO_PULSEGEN0_MUX2' in chapter Pulse Generator in the manual)

NOTE

The Pulse Generator is started when the combined signal of both start condition signals are true and a rising edge is detected. The invert parameter inverts the start condition state signal.

Parameters
  • signal (int): The start condition trigger signal
  • invert (bool): Invert the start condition trigger signal
Returns
  • int: The start condition trigger signal
def invert_start_condition(self, invert: bool = None) -> bool:
560    def invert_start_condition(self, invert : bool = None) -> bool:
561        """
562        Invert the start condition of the pulse generator
563
564        Parameters
565        ----------
566        invert : bool
567            Invert the start condition
568        
569        Returns
570        -------
571        bool
572            The start condition inversion
573        """
574
575        if invert is not None:
576            return_invert = self.config()
577            if invert:
578                return_invert |= SPCM_PULSEGEN_CONFIG_INVERT
579            else:
580                return_invert &= ~SPCM_PULSEGEN_CONFIG_INVERT
581            self.config(return_invert)
582        return ((self.config() & SPCM_PULSEGEN_CONFIG_INVERT) != 0)

Invert the start condition of the pulse generator

Parameters
  • invert (bool): Invert the start condition
Returns
  • bool: The start condition inversion
class PulseGenerators(spcm.CardFunctionality):
584class PulseGenerators(CardFunctionality):
585    """
586    a higher-level abstraction of the CardFunctionality class to implement Pulse generator functionality
587
588    Parameters
589    ----------
590    generators : list[PulseGenerator]
591        a list of pulse generators
592    num_generators : int
593        the number of pulse generators on the card
594    """
595    
596    generators : list[PulseGenerator]
597    num_generators = 4
598
599    def __init__(self, card : Card, enable : int = 0, *args, **kwargs) -> None:
600        """
601        The constructor of the PulseGenerators class
602
603        Parameters
604        ----------
605        card : Card
606            the card object that is used by the functionality
607        enable : int or bool
608            Enable or disable (all) the different pulse generators, by default all are turned off
609        
610        Raises
611        ------
612        SpcmException
613        """
614
615        super().__init__(card, *args, **kwargs)
616        # Check for the pulse generator option on the card
617        features = self.card.get_i(SPC_PCIEXTFEATURES)
618        if features & SPCM_FEAT_EXTFW_PULSEGEN:
619            self.card._print(f"Pulse generator option available")
620        else:
621            raise SpcmException(text="This card doesn't have the pulse generator functionality installed. Please contact sales@spec.de to get more information about this functionality.")
622        
623        self.load()
624        self.enable(enable)
625    
626    def load(self) -> None:
627        """Load the pulse generators"""
628        self.generators = [PulseGenerator(self.card, i) for i in range(self.num_generators)]
629
630    # TODO in the next driver release there will be a register to get the number of pulse generators
631    def get_num_generators(self) -> int:
632        """
633        Get the number of pulse generators on the card
634
635        Returns
636        -------
637        int
638            The number of pulse generators
639        """
640
641        return self.num_generators
642    
643    def __str__(self) -> str:
644        """
645        String representation of the PulseGenerators class
646    
647        Returns
648        -------
649        str
650            String representation of the PulseGenerators class
651        """
652        
653        return f"PulseGenerators(card={self.card})"
654    
655    __repr__ = __str__
656    
657
658    def __iter__(self) -> "PulseGenerators":
659        """Define this class as an iterator"""
660        return self
661    
662    def __getitem__(self, index : int) -> PulseGenerator:
663        """
664        Get the pulse generator at the given index
665
666        Parameters
667        ----------
668        index : int
669            The index of the pulse generator
670        
671        Returns
672        -------
673        PulseGenerator
674            The pulse generator at the given index
675        """
676
677        return self.generators[index]
678    
679    _generator_iterator_index = -1
680    def __next__(self) -> PulseGenerator:
681        """
682        This method is called when the next element is requested from the iterator
683
684        Returns
685        -------
686        PulseGenerator
687            the next available pulse generator
688        
689        Raises
690        ------
691        StopIteration
692        """
693        self._generator_iterator_index += 1
694        if self._generator_iterator_index >= self.num_generators:
695            self._generator_iterator_index = -1
696            raise StopIteration
697        return self.generators[self._generator_iterator_index]
698    
699    def __len__(self) -> int:
700        """Returns the number of available pulse generators"""
701        return len(self.generators)
702
703    # The pulse generator can be enabled or disabled
704    def enable(self, enable : int = None) -> int:
705        """
706        Enable or disable (all) the pulse generators (see register 'SPC_XIO_PULSEGEN_ENABLE' in chapter `Pulse Generator` in the manual)
707
708        Parameters
709        ----------
710        enable : int or bool
711            Enable or disable (all) the different pulse generators, by default all are turned off
712        
713        Returns
714        -------
715        int
716            The enable state of the pulse generators
717        """
718
719        
720        if isinstance(enable, bool):
721            enable = (1 << self.num_generators) - 1 if enable else 0
722        if enable is not None:
723            self.card.set_i(SPC_XIO_PULSEGEN_ENABLE, enable)
724        return self.card.get_i(SPC_XIO_PULSEGEN_ENABLE)
725    
726    def cmd(self, cmd : int) -> None:
727        """
728        Execute a command on the pulse generator (see register 'SPC_XIO_PULSEGEN_COMMAND' in chapter `Pulse Generator` in the manual)
729
730        Parameters
731        ----------
732        cmd : int
733            The command to execute
734        """
735        self.card.set_i(SPC_XIO_PULSEGEN_COMMAND, cmd)
736    
737    def force(self) -> None:
738        """
739        Generate a single rising edge, that is common for all pulse generator engines. This allows to start/trigger the output 
740        of all enabled pulse generators synchronously by issuing a software command. (see register 'SPC_XIO_PULSEGEN_COMMAND' in chapter `Pulse Generator` in the manual)
741        """
742        self.cmd(SPCM_PULSEGEN_CMD_FORCE)
743    
744    def write_setup(self) -> None:
745        """Write the setup of the pulse generator to the card"""
746        self.card.write_setup()
747    
748    # The clock rate in Hz of the pulse generator
749    def get_clock(self) -> int:
750        """
751        Get the clock rate of the pulse generator (see register 'SPC_XIO_PULSEGEN_CLOCK' in chapter `Pulse Generator` in the manual)
752
753        Returns
754        -------
755        int
756            The clock rate in Hz
757        """
758        return self.card.get_i(SPC_XIO_PULSEGEN_CLOCK)

a higher-level abstraction of the CardFunctionality class to implement Pulse generator functionality

Parameters
  • generators (list[PulseGenerator]): a list of pulse generators
  • num_generators (int): the number of pulse generators on the card
PulseGenerators(card: Card, enable: int = 0, *args, **kwargs)
599    def __init__(self, card : Card, enable : int = 0, *args, **kwargs) -> None:
600        """
601        The constructor of the PulseGenerators class
602
603        Parameters
604        ----------
605        card : Card
606            the card object that is used by the functionality
607        enable : int or bool
608            Enable or disable (all) the different pulse generators, by default all are turned off
609        
610        Raises
611        ------
612        SpcmException
613        """
614
615        super().__init__(card, *args, **kwargs)
616        # Check for the pulse generator option on the card
617        features = self.card.get_i(SPC_PCIEXTFEATURES)
618        if features & SPCM_FEAT_EXTFW_PULSEGEN:
619            self.card._print(f"Pulse generator option available")
620        else:
621            raise SpcmException(text="This card doesn't have the pulse generator functionality installed. Please contact sales@spec.de to get more information about this functionality.")
622        
623        self.load()
624        self.enable(enable)

The constructor of the PulseGenerators class

Parameters
  • card (Card): the card object that is used by the functionality
  • enable (int or bool): Enable or disable (all) the different pulse generators, by default all are turned off
Raises
  • SpcmException
generators: list[PulseGenerator]
num_generators = 4
def load(self) -> None:
626    def load(self) -> None:
627        """Load the pulse generators"""
628        self.generators = [PulseGenerator(self.card, i) for i in range(self.num_generators)]

Load the pulse generators

def get_num_generators(self) -> int:
631    def get_num_generators(self) -> int:
632        """
633        Get the number of pulse generators on the card
634
635        Returns
636        -------
637        int
638            The number of pulse generators
639        """
640
641        return self.num_generators

Get the number of pulse generators on the card

Returns
  • int: The number of pulse generators
def enable(self, enable: int = None) -> int:
704    def enable(self, enable : int = None) -> int:
705        """
706        Enable or disable (all) the pulse generators (see register 'SPC_XIO_PULSEGEN_ENABLE' in chapter `Pulse Generator` in the manual)
707
708        Parameters
709        ----------
710        enable : int or bool
711            Enable or disable (all) the different pulse generators, by default all are turned off
712        
713        Returns
714        -------
715        int
716            The enable state of the pulse generators
717        """
718
719        
720        if isinstance(enable, bool):
721            enable = (1 << self.num_generators) - 1 if enable else 0
722        if enable is not None:
723            self.card.set_i(SPC_XIO_PULSEGEN_ENABLE, enable)
724        return self.card.get_i(SPC_XIO_PULSEGEN_ENABLE)

Enable or disable (all) the pulse generators (see register 'SPC_XIO_PULSEGEN_ENABLE' in chapter Pulse Generator in the manual)

Parameters
  • enable (int or bool): Enable or disable (all) the different pulse generators, by default all are turned off
Returns
  • int: The enable state of the pulse generators
def cmd(self, cmd: int) -> None:
726    def cmd(self, cmd : int) -> None:
727        """
728        Execute a command on the pulse generator (see register 'SPC_XIO_PULSEGEN_COMMAND' in chapter `Pulse Generator` in the manual)
729
730        Parameters
731        ----------
732        cmd : int
733            The command to execute
734        """
735        self.card.set_i(SPC_XIO_PULSEGEN_COMMAND, cmd)

Execute a command on the pulse generator (see register 'SPC_XIO_PULSEGEN_COMMAND' in chapter Pulse Generator in the manual)

Parameters
  • cmd (int): The command to execute
def force(self) -> None:
737    def force(self) -> None:
738        """
739        Generate a single rising edge, that is common for all pulse generator engines. This allows to start/trigger the output 
740        of all enabled pulse generators synchronously by issuing a software command. (see register 'SPC_XIO_PULSEGEN_COMMAND' in chapter `Pulse Generator` in the manual)
741        """
742        self.cmd(SPCM_PULSEGEN_CMD_FORCE)

Generate a single rising edge, that is common for all pulse generator engines. This allows to start/trigger the output of all enabled pulse generators synchronously by issuing a software command. (see register 'SPC_XIO_PULSEGEN_COMMAND' in chapter Pulse Generator in the manual)

def write_setup(self) -> None:
744    def write_setup(self) -> None:
745        """Write the setup of the pulse generator to the card"""
746        self.card.write_setup()

Write the setup of the pulse generator to the card

def get_clock(self) -> int:
749    def get_clock(self) -> int:
750        """
751        Get the clock rate of the pulse generator (see register 'SPC_XIO_PULSEGEN_CLOCK' in chapter `Pulse Generator` in the manual)
752
753        Returns
754        -------
755        int
756            The clock rate in Hz
757        """
758        return self.card.get_i(SPC_XIO_PULSEGEN_CLOCK)

Get the clock rate of the pulse generator (see register 'SPC_XIO_PULSEGEN_CLOCK' in chapter Pulse Generator in the manual)

Returns
  • int: The clock rate in Hz
class Multi(spcm.DataTransfer):
 14class Multi(DataTransfer):
 15    """a high-level class to control Multiple Recording and Replay functionality on Spectrum Instrumentation cards
 16
 17    For more information about what setups are available, please have a look at the user manual
 18    for your specific card.
 19
 20    """
 21
 22    # Private
 23    _segment_size : int
 24    _num_segments : int
 25
 26    def __init__(self, card, *args, **kwargs) -> None:
 27        super().__init__(card, *args, **kwargs)
 28        self._segment_size = 0
 29        self._num_segments = 0
 30
 31    def segment_samples(self, segment_size : int = None) -> None:
 32        """
 33        Sets the memory size in samples per channel. The memory size setting must be set before transferring 
 34        data to the card. (see register `SPC_MEMSIZE` in the manual)
 35        
 36        Parameters
 37        ----------
 38        segment_size : int | pint.Quantity
 39            the size of a single segment in memory in Samples
 40        """
 41
 42        if segment_size is not None:
 43            segment_size = UnitConversion.convert(segment_size, units.S, int)
 44            self.card.set_i(SPC_SEGMENTSIZE, segment_size)
 45        segment_size = self.card.get_i(SPC_SEGMENTSIZE)
 46        self._segment_size = segment_size
 47    
 48    def post_trigger(self, num_samples : int = None) -> int:
 49        """
 50        Set the number of post trigger samples (see register `SPC_POSTTRIGGER` in the manual)
 51        
 52        Parameters
 53        ----------
 54        num_samples : int | pint.Quantity
 55            the number of post trigger samples
 56        
 57        Returns
 58        -------
 59        int
 60            the number of post trigger samples
 61        """
 62
 63        
 64        if self._segment_size < num_samples:
 65            raise ValueError(f"The number of post trigger samples needs to be smaller than the total number of samples in the segment: {self._segment_size} < {num_samples}")
 66        post_trigger = super().post_trigger(num_samples, set_pre_trigger=False)
 67        self._pre_trigger = self._segment_size - post_trigger
 68        return post_trigger
 69
 70    def allocate_buffer(self, segment_samples : int, num_segments : int = None) -> None:
 71        """
 72        Memory allocation for the buffer that is used for communicating with the card
 73
 74        Parameters
 75        ----------
 76        segment_samples : int | pint.Quantity
 77            use the number of samples and get the number of active channels and bytes per samples directly from the card
 78        num_segments : int = None
 79            the number of segments that are used for the multiple recording mode
 80        """
 81        
 82        segment_samples = UnitConversion.convert(segment_samples, units.S, int)
 83        num_segments = UnitConversion.convert(num_segments, units.S, int)
 84        self.segment_samples(segment_samples)
 85        if num_segments is None:
 86            self._num_segments = self._memory_size // segment_samples
 87        else:
 88            self._num_segments = num_segments
 89        
 90        super().allocate_buffer(segment_samples * self._num_segments, no_reshape=True)
 91
 92        num_channels = self.card.active_channels()
 93        if self.bits_per_sample > 1 and not self._12bit_mode:
 94            self.card._print(f"{self._num_segments} segments of {segment_samples} samples with {num_channels} channels")
 95            self.buffer = self.buffer.reshape((self._num_segments, segment_samples, num_channels), order='C') # index definition: [segment, sample, channel] !
 96
 97    def time_data(self, total_num_samples : int = None, return_units = units.s) -> npt.NDArray:
 98        """
 99        Get the time array for the data buffer
100
101        Parameters
102        ----------
103        total_num_samples : int | pint.Quantity
104            the total number of samples
105        return_units : pint.Unit
106            the units of the time array
107        
108        Returns
109        -------
110        numpy array
111            the time array
112        """
113
114        if total_num_samples is None:
115            total_num_samples = self._buffer_samples // self._num_segments
116        return super().time_data(total_num_samples, return_units)
117
118    def unpack_12bit_buffer(self, data : npt.NDArray[np.int_] = None) -> npt.NDArray[np.int_]:
119        """
120        Unpacks the 12-bit packed data to 16-bit data
121
122        Parameters
123        ----------
124        data : numpy array
125            the packed data
126
127        Returns
128        -------
129        numpy array
130            the unpacked 16bit buffer
131        """
132        buffer_12bit = super().unpack_12bit_buffer(data)
133        return buffer_12bit.reshape((self._num_segments, self.num_channels, self._segment_size), order='C')
134
135    
136    def __next__(self) -> npt.ArrayLike:
137        """
138        This method is called when the next element is requested from the iterator
139
140        Returns
141        -------
142        npt.ArrayLike
143            the next data block
144        
145        Raises
146        ------
147        StopIteration
148        """
149        super().__next__()
150        user_pos = self.avail_user_pos()
151        current_segment = user_pos // self._segment_size
152        current_pos_in_segment = user_pos % self._segment_size
153        final_segment = ((user_pos+self._notify_samples) // self._segment_size)
154        final_pos_in_segment = (user_pos+self._notify_samples) % self._segment_size
155
156        self.card._print("NumSamples = {}, CurrentSegment = {}, CurrentPos = {},  FinalSegment = {}, FinalPos = {}".format(self._notify_samples, current_segment, current_pos_in_segment, final_segment, final_pos_in_segment))
157
158        return self.buffer[current_segment:final_segment, :, :]

a high-level class to control Multiple Recording and Replay functionality on Spectrum Instrumentation cards

For more information about what setups are available, please have a look at the user manual for your specific card.

Multi(card, *args, **kwargs)
26    def __init__(self, card, *args, **kwargs) -> None:
27        super().__init__(card, *args, **kwargs)
28        self._segment_size = 0
29        self._num_segments = 0

Initialize the DataTransfer object with a card object and additional arguments

Parameters
  • card (Card): the card object that is used for the data transfer
  • *args (list): list of additional arguments
  • **kwargs (dict): dictionary of additional keyword arguments
def segment_samples(self, segment_size: int = None) -> None:
31    def segment_samples(self, segment_size : int = None) -> None:
32        """
33        Sets the memory size in samples per channel. The memory size setting must be set before transferring 
34        data to the card. (see register `SPC_MEMSIZE` in the manual)
35        
36        Parameters
37        ----------
38        segment_size : int | pint.Quantity
39            the size of a single segment in memory in Samples
40        """
41
42        if segment_size is not None:
43            segment_size = UnitConversion.convert(segment_size, units.S, int)
44            self.card.set_i(SPC_SEGMENTSIZE, segment_size)
45        segment_size = self.card.get_i(SPC_SEGMENTSIZE)
46        self._segment_size = segment_size

Sets the memory size in samples per channel. The memory size setting must be set before transferring data to the card. (see register SPC_MEMSIZE in the manual)

Parameters
  • segment_size (int | pint.Quantity): the size of a single segment in memory in Samples
def post_trigger(self, num_samples: int = None) -> int:
48    def post_trigger(self, num_samples : int = None) -> int:
49        """
50        Set the number of post trigger samples (see register `SPC_POSTTRIGGER` in the manual)
51        
52        Parameters
53        ----------
54        num_samples : int | pint.Quantity
55            the number of post trigger samples
56        
57        Returns
58        -------
59        int
60            the number of post trigger samples
61        """
62
63        
64        if self._segment_size < num_samples:
65            raise ValueError(f"The number of post trigger samples needs to be smaller than the total number of samples in the segment: {self._segment_size} < {num_samples}")
66        post_trigger = super().post_trigger(num_samples, set_pre_trigger=False)
67        self._pre_trigger = self._segment_size - post_trigger
68        return post_trigger

Set the number of post trigger samples (see register SPC_POSTTRIGGER in the manual)

Parameters
  • num_samples (int | pint.Quantity): the number of post trigger samples
Returns
  • int: the number of post trigger samples
def allocate_buffer(self, segment_samples: int, num_segments: int = None) -> None:
70    def allocate_buffer(self, segment_samples : int, num_segments : int = None) -> None:
71        """
72        Memory allocation for the buffer that is used for communicating with the card
73
74        Parameters
75        ----------
76        segment_samples : int | pint.Quantity
77            use the number of samples and get the number of active channels and bytes per samples directly from the card
78        num_segments : int = None
79            the number of segments that are used for the multiple recording mode
80        """
81        
82        segment_samples = UnitConversion.convert(segment_samples, units.S, int)
83        num_segments = UnitConversion.convert(num_segments, units.S, int)
84        self.segment_samples(segment_samples)
85        if num_segments is None:
86            self._num_segments = self._memory_size // segment_samples
87        else:
88            self._num_segments = num_segments
89        
90        super().allocate_buffer(segment_samples * self._num_segments, no_reshape=True)
91
92        num_channels = self.card.active_channels()
93        if self.bits_per_sample > 1 and not self._12bit_mode:
94            self.card._print(f"{self._num_segments} segments of {segment_samples} samples with {num_channels} channels")
95            self.buffer = self.buffer.reshape((self._num_segments, segment_samples, num_channels), order='C') # index definition: [segment, sample, channel] !

Memory allocation for the buffer that is used for communicating with the card

Parameters
  • segment_samples (int | pint.Quantity): use the number of samples and get the number of active channels and bytes per samples directly from the card
  • num_segments (int = None): the number of segments that are used for the multiple recording mode
def time_data( self, total_num_samples: int = None, return_units=<Unit('second')>) -> numpy.ndarray[tuple[int, ...], numpy.dtype[+_ScalarType_co]]:
 97    def time_data(self, total_num_samples : int = None, return_units = units.s) -> npt.NDArray:
 98        """
 99        Get the time array for the data buffer
100
101        Parameters
102        ----------
103        total_num_samples : int | pint.Quantity
104            the total number of samples
105        return_units : pint.Unit
106            the units of the time array
107        
108        Returns
109        -------
110        numpy array
111            the time array
112        """
113
114        if total_num_samples is None:
115            total_num_samples = self._buffer_samples // self._num_segments
116        return super().time_data(total_num_samples, return_units)

Get the time array for the data buffer

Parameters
  • total_num_samples (int | pint.Quantity): the total number of samples
  • return_units (pint.Unit): the units of the time array
Returns
  • numpy array: the time array
def unpack_12bit_buffer( self, data: numpy.ndarray[tuple[int, ...], numpy.dtype[numpy.int64]] = None) -> numpy.ndarray[tuple[int, ...], numpy.dtype[numpy.int64]]:
118    def unpack_12bit_buffer(self, data : npt.NDArray[np.int_] = None) -> npt.NDArray[np.int_]:
119        """
120        Unpacks the 12-bit packed data to 16-bit data
121
122        Parameters
123        ----------
124        data : numpy array
125            the packed data
126
127        Returns
128        -------
129        numpy array
130            the unpacked 16bit buffer
131        """
132        buffer_12bit = super().unpack_12bit_buffer(data)
133        return buffer_12bit.reshape((self._num_segments, self.num_channels, self._segment_size), order='C')

Unpacks the 12-bit packed data to 16-bit data

Parameters
  • data (numpy array): the packed data
Returns
  • numpy array: the unpacked 16bit buffer
class Gated(spcm.DataTransfer):
 17class Gated(DataTransfer):
 18    """
 19    A high-level class to control Gated sampling Spectrum Instrumentation cards.
 20
 21    For more information about what setups are available, please have a look at the user manual
 22    for your specific card.
 23    """
 24
 25    max_num_gates : int = 128
 26    timestamp : TimeStamp = None
 27
 28    # Private
 29    _pre_trigger : int = 0
 30    _post_trigger : int = 0
 31
 32    _timestamp : TimeStamp = None
 33    _alignment : int = 0
 34
 35    # private FIFO mode settings
 36    _fifo_mode : bool = False
 37    _num_gates : int = 0
 38
 39    def __init__(self, card, *args, **kwargs) -> None:
 40        super().__init__(card, *args, **kwargs)
 41        if self.direction is Direction.Acquisition:
 42            self.max_num_gates = kwargs.get("max_num_gates", 128)
 43            if card.card_mode() == SPC_REC_FIFO_GATE:
 44                print("Gated acquisition mode is set to FIFO mode")
 45                self._fifo_mode = True
 46                self.num_gates(kwargs.get("num_gates", 0))
 47                if self._num_gates > 0:
 48                    self.max_num_gates = self._num_gates
 49            self.timestamp = TimeStamp(card)
 50            self.timestamp.mode(SPC_TSMODE_STARTRESET, SPC_TSCNT_INTERNAL)
 51            self.timestamp.allocate_buffer(2*self.max_num_gates+1)
 52            # self.card.cmd(M2CMD_EXTRA_POLL)
 53            self._alignment = self.card.get_i(SPC_GATE_LEN_ALIGNMENT)
 54    
 55    def start_buffer_transfer(self, *args, **kwargs):
 56        super().start_buffer_transfer(*args, **kwargs)
 57        if self.direction is Direction.Acquisition:
 58            if self._polling:
 59                self.timestamp.start_buffer_transfer(M2CMD_EXTRA_POLL)
 60            else:
 61                self.timestamp.start_buffer_transfer(M2CMD_EXTRA_STARTDMA)
 62    
 63    def post_trigger(self, num_samples : int = None) -> int:
 64        """
 65        Set the number of post trigger samples (see register `SPC_POSTTRIGGER` in the manual)
 66        
 67        Parameters
 68        ----------
 69        num_samples : int | pint.Quantity
 70            the number of post trigger samples
 71        
 72        Returns
 73        -------
 74        int
 75            the number of post trigger samples
 76        """
 77
 78        if not self._fifo_mode and self._memory_size < num_samples:
 79            raise ValueError("The number of post trigger samples needs to be smaller than the total number of samples")
 80        if num_samples is not None:
 81            num_samples = UnitConversion.convert(num_samples, units.Sa, int)
 82            self.card.set_i(SPC_POSTTRIGGER, num_samples)
 83        self._post_trigger = self.card.get_i(SPC_POSTTRIGGER)
 84        return self._post_trigger
 85
 86    def alignment(self) -> int:
 87        """
 88        Get the alignment of the end of the gated data buffer (see register `SPC_GATE_LEN_ALIGNMENT` in the manual)
 89
 90        Returns
 91        -------
 92        int
 93            the number of samples to align the end of the gated data buffer
 94        """
 95        return self._alignment
 96    
 97    def num_gates(self, num_gates : int = None) -> int:
 98        """
 99        FIFO only: set the number of gates to be acquired (see register `SPC_LOOPS` in the manual)
100
101        Parameters
102        ----------
103        num_gates : int
104            the number of gates to be acquired
105
106        Returns
107        -------
108        int
109            the number of gates to be acquired
110        """
111        
112        if num_gates is not None:
113            self.card.set_i(SPC_LOOPS, num_gates)
114        self._num_gates = self.card.get_i(SPC_LOOPS)
115        return self._num_gates
116    
117    def gate_counter(self) -> int:
118        """
119        Get the number of gate events since acquisition start (see register 'SPC_TRIGGERCOUNTER' in chapter `Trigger` in the manual)
120        
121        Returns
122        -------
123        int
124            The gate counter
125        """
126
127        return self.card.get_i(SPC_TRIGGERCOUNTER)
128    
129    def available_gates(self) -> tuple[int, int]:
130        """
131        Get the number of available gates in the timestamp buffer
132
133        Returns
134        -------
135        tuple(int, int)
136            The current position and the number of available gates from the timestamp buffer
137        """
138        
139        return self.timestamp.avail_user_pos() // 2, self.timestamp.avail_user_len() // 2
140    
141    def __iter__(self):
142        """
143        Returns an iterator object and initializes the iterator index.
144
145        Returns
146        -------
147        iterable
148            An iterator object for the class.
149        """
150        iter = super().__iter__()
151        self.iterator_index = -1
152        return iter
153    
154    iterator_index : int = -1
155    gate_count : int = 0
156    _start : int = 0
157    _end : int = 0
158    _aligned_end : int = 0
159    _current_num_samples : int = 0
160    def __next__(self):
161        if self.direction is not Direction.Acquisition:
162            raise ValueError("Iterating the Gated class can only be used with acquisition")
163        if self._fifo_mode and not self._polling:
164            raise SpcmException("Polling is required for fifo gated acquisition. Please set the polling mode to True")
165
166        self.iterator_index += 1
167
168        # notify the card that data is available or read, but only after the first block
169        if self.iterator_index > 0 and self._auto_avail_card_len:
170            self.flush()
171
172        # Check if all the gates have been acquired
173        if self._num_gates > 0 and self.iterator_index >= self._num_gates or self.iterator_index >= self.max_num_gates: self.stop_next()
174        ts_len = self.timestamp.avail_user_len()
175        if not self._fifo_mode and ts_len < 2: self.stop_next()
176        
177        while self._polling:
178            ts_len = self.timestamp.avail_user_len()
179            self.card._print(f"Available time stamps: {ts_len}", end="\r")
180            if ts_len >= 2:
181                break
182            time.sleep(self._polling_timer)
183
184        # Get the start and end of the gate event
185        self._start = self.avail_user_pos()
186        length = self.timestamp.buffer[2*self.iterator_index+1, 0] - self.timestamp.buffer[2*self.iterator_index+0, 0]
187        self.card._print(f"Gate {self.iterator_index} - Start: {self._start} - Length: {length}", end="\n")
188        segment_length = length + self._pre_trigger + self._post_trigger
189        self._end = self._start + segment_length
190
191        # The data end of the gate is aligned to a multiple of the alignment length, hence we have to calculate the aligned end of the gate to know where the next gate starts
192        alignment = self.alignment()
193        length_with_alignment = (length // alignment + 1) * alignment
194        self._current_num_samples = length_with_alignment + self._pre_trigger + self._post_trigger
195        self._aligned_end = self._start + self._current_num_samples
196
197        force_triggers_added = 0
198        # Wait for enough data to be available in the buffer to get the next gate
199        while self._polling:
200            user_len = self.avail_user_len()
201            self.card._print(f"Available data: {user_len} - Required data: {self._current_num_samples}", end="\r")
202            if user_len >= self._current_num_samples:
203                break
204            # Check if the last gate has been reached, if so add force triggers to get enough data in the FPGA FIFO to output the next gate
205            if self._num_gates > 0 and self.iterator_index == self._num_gates - 1:
206                self.card.cmd(M2CMD_CARD_FORCETRIGGER)
207                force_triggers_added += 1
208
209            time.sleep(self._polling_timer)
210        
211        if force_triggers_added > 0:
212            self.card._print(f"\nForce triggers {force_triggers_added} added to get enough data for the last gate")
213
214        self._current_samples += self._current_num_samples
215        if self._to_transfer_samples > 0 and self._to_transfer_samples <= self._current_samples:
216            self.stop_next()
217        
218        # Check for overflowing of the data buffer
219        if self._end > self.buffer_samples:
220            self.card._print(f"Warning: The end of the gate ({self._end}) exceeds the buffer size ({self.buffer_samples}). This might lead to speed loss.")
221            sample_indices = np.mod(np.arange(self._start, self._end), self.buffer_samples)
222            return self.buffer[:, sample_indices]
223
224        # Return the view of the data buffer that contains only the data of the current gate
225        return self.buffer[:, self._start:self._end]
226    
227    def polling(self, polling : bool = True, timer : int = 0.01 * units.s):
228        """
229        Set the polling mode for the gated acquisition
230
231        Parameters
232        ----------
233        polling : bool
234            If True, the polling mode is enabled, otherwise it is disabled
235        timer : int | pint.Quantity
236            The timer to wait for new data in the buffer in polling mode
237
238        Returns
239        -------
240        bool
241            The current polling mode
242        """
243        
244        super().polling(polling, timer)
245        self.notify_samples(64 * units.S)  # Set the notify size to the smallest possible value (a multiple of 64 bytes is the minimum for M5i cards)
246
247    def stop_next(self):
248        """
249        Stop the iteration and flush all the iterator parameters
250        """
251        self.iterator_index = -1
252        self._start = 0
253        self._end = 0
254        self._aligned_end = 0
255        self._current_samples = 0
256        self._current_num_samples = 0
257        raise StopIteration
258    
259    def flush(self):
260        """
261        This method is used to tell the card that a notify size of data is freed up after reading (acquisition) or written to (generation)
262        """
263        self.avail_card_len(self._current_num_samples)
264        self.timestamp.avail_card_len(2) # two time stamps per gate
265    
266    def current_time_range(self, return_unit = None) -> int:
267        """
268        Get the current time range of the data buffer
269
270        Parameters
271        ----------
272        return_unit : pint.Unit
273            the unit to return the time range in
274
275        Returns
276        -------
277        int or pint.Quantity
278            the current time range of the data buffer
279        """
280
281        current_length = self._end - self._start
282        time_range = np.arange(self.timestamp.buffer[2*self.iterator_index+0, 0] - self._pre_trigger, self.timestamp.buffer[2*self.iterator_index+1, 0] + self._post_trigger)
283        time_range = UnitConversion.to_unit(time_range[:current_length] / self._sample_rate(), return_unit)
284        return time_range
285
286    def current_timestamps(self, return_unit = None) -> tuple:
287        """
288        Get the current timestamps of the data buffer
289
290        Parameters
291        ----------
292        return_unit : pint.Unit
293            the unit to return the timestamps in
294
295        Returns
296        -------
297        tuple
298            the current timestamps of the data buffer
299        """
300
301        ts_start = self.timestamp.buffer[2*self.iterator_index+0, 0]
302        ts_end = self.timestamp.buffer[2*self.iterator_index+1, 0]
303        ts_start = UnitConversion.to_unit(ts_start / self._sample_rate(), return_unit)
304        ts_end = UnitConversion.to_unit(ts_end / self._sample_rate(), return_unit)
305        return ts_start, ts_end

A high-level class to control Gated sampling Spectrum Instrumentation cards.

For more information about what setups are available, please have a look at the user manual for your specific card.

Gated(card, *args, **kwargs)
39    def __init__(self, card, *args, **kwargs) -> None:
40        super().__init__(card, *args, **kwargs)
41        if self.direction is Direction.Acquisition:
42            self.max_num_gates = kwargs.get("max_num_gates", 128)
43            if card.card_mode() == SPC_REC_FIFO_GATE:
44                print("Gated acquisition mode is set to FIFO mode")
45                self._fifo_mode = True
46                self.num_gates(kwargs.get("num_gates", 0))
47                if self._num_gates > 0:
48                    self.max_num_gates = self._num_gates
49            self.timestamp = TimeStamp(card)
50            self.timestamp.mode(SPC_TSMODE_STARTRESET, SPC_TSCNT_INTERNAL)
51            self.timestamp.allocate_buffer(2*self.max_num_gates+1)
52            # self.card.cmd(M2CMD_EXTRA_POLL)
53            self._alignment = self.card.get_i(SPC_GATE_LEN_ALIGNMENT)

Initialize the DataTransfer object with a card object and additional arguments

Parameters
  • card (Card): the card object that is used for the data transfer
  • *args (list): list of additional arguments
  • **kwargs (dict): dictionary of additional keyword arguments
max_num_gates: int = 128
timestamp: TimeStamp = None
def start_buffer_transfer(self, *args, **kwargs):
55    def start_buffer_transfer(self, *args, **kwargs):
56        super().start_buffer_transfer(*args, **kwargs)
57        if self.direction is Direction.Acquisition:
58            if self._polling:
59                self.timestamp.start_buffer_transfer(M2CMD_EXTRA_POLL)
60            else:
61                self.timestamp.start_buffer_transfer(M2CMD_EXTRA_STARTDMA)

Start the transfer of the data to or from the card (see the API function spcm_dwDefTransfer_i64 in the manual)

Parameters
  • *args (list): list of additonal arguments that are added as flags to the start dma command
  • buffer_type (int): the type of buffer that is used for the transfer
  • direction (int): the direction of the transfer
  • notify_samples (int): the number of samples to notify the user about
  • transfer_offset (int): the offset of the transfer
  • transfer_length (int): the length of the transfer
  • exception_num_samples (bool): if True, an exception is raised if the number of samples is not a multiple of the notify samples. The automatic buffer handling only works with the number of samples being a multiple of the notify samples.
Raises
  • SpcmException
def post_trigger(self, num_samples: int = None) -> int:
63    def post_trigger(self, num_samples : int = None) -> int:
64        """
65        Set the number of post trigger samples (see register `SPC_POSTTRIGGER` in the manual)
66        
67        Parameters
68        ----------
69        num_samples : int | pint.Quantity
70            the number of post trigger samples
71        
72        Returns
73        -------
74        int
75            the number of post trigger samples
76        """
77
78        if not self._fifo_mode and self._memory_size < num_samples:
79            raise ValueError("The number of post trigger samples needs to be smaller than the total number of samples")
80        if num_samples is not None:
81            num_samples = UnitConversion.convert(num_samples, units.Sa, int)
82            self.card.set_i(SPC_POSTTRIGGER, num_samples)
83        self._post_trigger = self.card.get_i(SPC_POSTTRIGGER)
84        return self._post_trigger

Set the number of post trigger samples (see register SPC_POSTTRIGGER in the manual)

Parameters
  • num_samples (int | pint.Quantity): the number of post trigger samples
Returns
  • int: the number of post trigger samples
def alignment(self) -> int:
86    def alignment(self) -> int:
87        """
88        Get the alignment of the end of the gated data buffer (see register `SPC_GATE_LEN_ALIGNMENT` in the manual)
89
90        Returns
91        -------
92        int
93            the number of samples to align the end of the gated data buffer
94        """
95        return self._alignment

Get the alignment of the end of the gated data buffer (see register SPC_GATE_LEN_ALIGNMENT in the manual)

Returns
  • int: the number of samples to align the end of the gated data buffer
def num_gates(self, num_gates: int = None) -> int:
 97    def num_gates(self, num_gates : int = None) -> int:
 98        """
 99        FIFO only: set the number of gates to be acquired (see register `SPC_LOOPS` in the manual)
100
101        Parameters
102        ----------
103        num_gates : int
104            the number of gates to be acquired
105
106        Returns
107        -------
108        int
109            the number of gates to be acquired
110        """
111        
112        if num_gates is not None:
113            self.card.set_i(SPC_LOOPS, num_gates)
114        self._num_gates = self.card.get_i(SPC_LOOPS)
115        return self._num_gates

FIFO only: set the number of gates to be acquired (see register SPC_LOOPS in the manual)

Parameters
  • num_gates (int): the number of gates to be acquired
Returns
  • int: the number of gates to be acquired
def gate_counter(self) -> int:
117    def gate_counter(self) -> int:
118        """
119        Get the number of gate events since acquisition start (see register 'SPC_TRIGGERCOUNTER' in chapter `Trigger` in the manual)
120        
121        Returns
122        -------
123        int
124            The gate counter
125        """
126
127        return self.card.get_i(SPC_TRIGGERCOUNTER)

Get the number of gate events since acquisition start (see register 'SPC_TRIGGERCOUNTER' in chapter Trigger in the manual)

Returns
  • int: The gate counter
def available_gates(self) -> tuple[int, int]:
129    def available_gates(self) -> tuple[int, int]:
130        """
131        Get the number of available gates in the timestamp buffer
132
133        Returns
134        -------
135        tuple(int, int)
136            The current position and the number of available gates from the timestamp buffer
137        """
138        
139        return self.timestamp.avail_user_pos() // 2, self.timestamp.avail_user_len() // 2

Get the number of available gates in the timestamp buffer

Returns
  • tuple(int, int): The current position and the number of available gates from the timestamp buffer
iterator_index: int = -1
gate_count: int = 0
def polling(self, polling: bool = True, timer: int = <Quantity(0.01, 'second')>):
227    def polling(self, polling : bool = True, timer : int = 0.01 * units.s):
228        """
229        Set the polling mode for the gated acquisition
230
231        Parameters
232        ----------
233        polling : bool
234            If True, the polling mode is enabled, otherwise it is disabled
235        timer : int | pint.Quantity
236            The timer to wait for new data in the buffer in polling mode
237
238        Returns
239        -------
240        bool
241            The current polling mode
242        """
243        
244        super().polling(polling, timer)
245        self.notify_samples(64 * units.S)  # Set the notify size to the smallest possible value (a multiple of 64 bytes is the minimum for M5i cards)

Set the polling mode for the gated acquisition

Parameters
  • polling (bool): If True, the polling mode is enabled, otherwise it is disabled
  • timer (int | pint.Quantity): The timer to wait for new data in the buffer in polling mode
Returns
  • bool: The current polling mode
def stop_next(self):
247    def stop_next(self):
248        """
249        Stop the iteration and flush all the iterator parameters
250        """
251        self.iterator_index = -1
252        self._start = 0
253        self._end = 0
254        self._aligned_end = 0
255        self._current_samples = 0
256        self._current_num_samples = 0
257        raise StopIteration

Stop the iteration and flush all the iterator parameters

def flush(self):
259    def flush(self):
260        """
261        This method is used to tell the card that a notify size of data is freed up after reading (acquisition) or written to (generation)
262        """
263        self.avail_card_len(self._current_num_samples)
264        self.timestamp.avail_card_len(2) # two time stamps per gate

This method is used to tell the card that a notify size of data is freed up after reading (acquisition) or written to (generation)

def current_time_range(self, return_unit=None) -> int:
266    def current_time_range(self, return_unit = None) -> int:
267        """
268        Get the current time range of the data buffer
269
270        Parameters
271        ----------
272        return_unit : pint.Unit
273            the unit to return the time range in
274
275        Returns
276        -------
277        int or pint.Quantity
278            the current time range of the data buffer
279        """
280
281        current_length = self._end - self._start
282        time_range = np.arange(self.timestamp.buffer[2*self.iterator_index+0, 0] - self._pre_trigger, self.timestamp.buffer[2*self.iterator_index+1, 0] + self._post_trigger)
283        time_range = UnitConversion.to_unit(time_range[:current_length] / self._sample_rate(), return_unit)
284        return time_range

Get the current time range of the data buffer

Parameters
  • return_unit (pint.Unit): the unit to return the time range in
Returns
  • int or pint.Quantity: the current time range of the data buffer
def current_timestamps(self, return_unit=None) -> tuple:
286    def current_timestamps(self, return_unit = None) -> tuple:
287        """
288        Get the current timestamps of the data buffer
289
290        Parameters
291        ----------
292        return_unit : pint.Unit
293            the unit to return the timestamps in
294
295        Returns
296        -------
297        tuple
298            the current timestamps of the data buffer
299        """
300
301        ts_start = self.timestamp.buffer[2*self.iterator_index+0, 0]
302        ts_end = self.timestamp.buffer[2*self.iterator_index+1, 0]
303        ts_start = UnitConversion.to_unit(ts_start / self._sample_rate(), return_unit)
304        ts_end = UnitConversion.to_unit(ts_end / self._sample_rate(), return_unit)
305        return ts_start, ts_end

Get the current timestamps of the data buffer

Parameters
  • return_unit (pint.Unit): the unit to return the timestamps in
Returns
  • tuple: the current timestamps of the data buffer
class TimeStamp(spcm.DataTransfer):
 14class TimeStamp(DataTransfer):
 15    """a class to control Spectrum Instrumentation cards with the timestamp functionality
 16
 17    For more information about what setups are available, please have a look at the user manual
 18    for your specific card
 19
 20    Parameters
 21    ----------
 22    ts_mode : int
 23    transfer_mode : int
 24    bits_per_ts : int = 0
 25    bytes_per_ts : int = 16
 26    """
 27    ts_mode : int
 28    transfer_mode : int
 29
 30    bits_per_ts : int = 0
 31    bytes_per_ts : int = 16
 32    
 33    _notify_timestamps : int = 0
 34    _to_transfer_timestamps : int = 0
 35
 36    def __init__(self, card, *args, **kwargs) -> None:
 37        """
 38        Initialize the TimeStamp object with a card object
 39
 40        Parameters
 41        ----------
 42        card : Card
 43            a card object that is used to control the card
 44        """
 45        
 46        super().__init__(card, *args, **kwargs)
 47        self.buffer_type = SPCM_BUF_TIMESTAMP
 48        self.bits_per_ts = self.bytes_per_ts * 8
 49    
 50    def cmd(self, *args) -> None:
 51        """
 52        Execute spcm timestamp commands (see register 'SPC_TIMESTAMP_CMD' in chapter `Timestamp` in the manual)
 53    
 54        Parameters
 55        ----------
 56        *args : int
 57            The different timestamp command flags to be executed.
 58        """
 59
 60        cmd = 0
 61        for arg in args:
 62            cmd |= arg
 63        self.card.set_i(SPC_TIMESTAMP_CMD, cmd)
 64    
 65    def reset(self) -> None:
 66        """Reset the timestamp counter (see command 'SPC_TS_RESET' in chapter `Timestamp` in the manual)"""
 67        self.cmd(SPC_TS_RESET)
 68    
 69    def mode(self, mode : int, *args : list[int]) -> None:
 70        """
 71        Set the mode of the timestamp counter (see register 'SPC_TIMESTAMP_CMD' in chapter `Timestamp` in the manual)
 72
 73        Parameters
 74        ----------
 75        mode : int
 76            The mode of the timestamp counter
 77        *args : list[int]
 78            List of additional commands send with setting the mode
 79        """
 80        self.ts_mode = mode
 81        self.cmd(self.ts_mode, *args)
 82
 83    def notify_timestamps(self, notify_timestamps : int) -> None:
 84        """
 85        Set the number of timestamps to notify the user about
 86        
 87        Parameters
 88        ----------
 89        notify_timestamps : int
 90            the number of timestamps to notify the user about
 91        """
 92        self._notify_timestamps = notify_timestamps
 93    
 94    def allocate_buffer(self, num_timestamps : int) -> None:
 95        """
 96        Allocate the buffer for the timestamp data transfer
 97        
 98        Parameters
 99        ----------
100        num_timestamps : int
101            The number of timestamps to be allocated
102        """
103
104        num_timestamps = UnitConversion.convert(num_timestamps, units.S, int)
105        self.buffer_size = num_timestamps * self.bytes_per_ts
106
107        dwMask = self._buffer_alignment - 1
108
109        sample_type = np.int64
110        item_size = sample_type(0).itemsize
111        # allocate a buffer (numpy array) for DMA transfer: a little bigger one to have room for address alignment
112        databuffer_unaligned = np.zeros(((self._buffer_alignment + self.buffer_size) // item_size, ), dtype = sample_type)   # half byte count at int16 sample (// = integer division)
113        # two numpy-arrays may share the same memory: skip the begin up to the alignment boundary (ArrayVariable[SKIP_VALUE:])
114        # Address of data-memory from numpy-array: ArrayVariable.__array_interface__['data'][0]
115        start_pos_samples = ((self._buffer_alignment - (databuffer_unaligned.__array_interface__['data'][0] & dwMask)) // item_size)
116        self.buffer = databuffer_unaligned[start_pos_samples:start_pos_samples + (self.buffer_size // item_size)]   # byte address but int16 sample: therefore / 2
117        self.buffer = self.buffer.reshape((num_timestamps, 2), order='C') # array items per timestamp, because the maximum item size is 8 bytes = 64 bits
118    
119    def start_buffer_transfer(self, *args, direction=SPCM_DIR_CARDTOPC, notify_timestamps = None, transfer_offset=0, transfer_length=None) -> None:
120        """
121        Start the transfer of the timestamp data to the card
122        
123        Parameters
124        ----------
125        *args : list
126            list of additonal arguments that are added as flags to the start dma command
127        """
128        
129        notify_size = 0
130        if notify_timestamps is not None: 
131            self._notify_timestamps = notify_timestamps
132        if self._notify_timestamps: 
133            notify_size = self._notify_timestamps * self.bytes_per_ts
134
135        if transfer_offset:
136            transfer_offset_bytes = transfer_offset * self.bytes_per_ts
137        else:
138            transfer_offset_bytes = 0
139
140        if transfer_length is not None: 
141            transfer_length_bytes = transfer_length * self.bytes_per_ts
142        else:
143            transfer_length_bytes = self.buffer_size
144
145
146        # we define the buffer for transfer and start the DMA transfer
147        self.card._print("Starting the Timestamp transfer and waiting until data is in board memory")
148        self._c_buffer = self.buffer.ctypes.data_as(c_void_p)
149        spcm_dwDefTransfer_i64(self.card._handle, self.buffer_type, direction, notify_size, self._c_buffer, transfer_offset_bytes, transfer_length_bytes)
150        cmd = 0
151        for arg in args:
152            cmd |= arg
153        self.card.cmd(cmd)
154        self.card._print("... timestamp data transfer started")
155    
156    def avail_card_len(self, num_timestamps : int) -> None:
157        """
158        Set the amount of timestamps that is available for reading of the timestamp buffer (see register 'SPC_TS_AVAIL_CARD_LEN' in chapter `Timestamp` in the manual)
159
160        Parameters
161        ----------
162        num_timestamps : int
163            the amount of timestamps that is available for reading
164        """
165        card_len = num_timestamps * self.bytes_per_ts
166        self.card.set_i(SPC_TS_AVAIL_CARD_LEN, card_len)
167    
168    def avail_user_pos(self) -> int:
169        """
170        Get the current position of the pointer in the timestamp buffer (see register 'SPC_TS_AVAIL_USER_POS' in chapter `Timestamp` in the manual)
171
172        Returns
173        -------
174        int
175            pointer position in timestamps
176        """
177        return self.card.get_i(SPC_TS_AVAIL_USER_POS) // self.bytes_per_ts
178    
179    def avail_user_len(self) -> int:
180        """
181        Get the current length of the data in timestamps in the timestamp buffer (see register 'SPC_TS_AVAIL_USER_LEN' in chapter `Timestamp` in the manual)
182
183        Returns
184        -------
185        int
186            data length available in number of timestamps
187        """
188        return self.card.get_i(SPC_TS_AVAIL_USER_LEN) // self.bytes_per_ts
189    
190
191    # Iterator methods
192    _max_polling = 64
193
194    def to_transfer_timestamps(self, timestamps: int) -> None:
195        """
196        This method sets the number of timestamps to transfer
197
198        Parameters
199        ----------
200        timestamps : int
201            the number of timestamps to transfer
202        """
203        self._to_transfer_timestamps = timestamps
204
205    def poll(self, polling_length : int = 0) -> npt.ArrayLike:
206        """
207        This method is called when polling for timestamps
208
209        Parameters
210        ----------
211        polling_length : int = 0
212            the number of timestamps to poll and wait for
213
214        Returns
215        -------
216        npt.ArrayLike
217            the next data block
218        """
219
220        while True:
221            user_pos = self.avail_user_pos()
222            user_len = self.avail_user_len()
223            if not polling_length and user_len >= 1:
224                self.avail_card_len(user_len)
225                return self.buffer[user_pos:user_pos+user_len, :]
226            elif user_len >= polling_length:
227                self.avail_card_len(polling_length)
228                return self.buffer[user_pos:user_pos+polling_length, :]

a class to control Spectrum Instrumentation cards with the timestamp functionality

For more information about what setups are available, please have a look at the user manual for your specific card

Parameters
  • ts_mode (int):

  • transfer_mode (int):

  • bits_per_ts (int = 0):

  • bytes_per_ts (int = 16):

TimeStamp(card, *args, **kwargs)
36    def __init__(self, card, *args, **kwargs) -> None:
37        """
38        Initialize the TimeStamp object with a card object
39
40        Parameters
41        ----------
42        card : Card
43            a card object that is used to control the card
44        """
45        
46        super().__init__(card, *args, **kwargs)
47        self.buffer_type = SPCM_BUF_TIMESTAMP
48        self.bits_per_ts = self.bytes_per_ts * 8

Initialize the TimeStamp object with a card object

Parameters
  • card (Card): a card object that is used to control the card
ts_mode: int
transfer_mode: int
bits_per_ts: int = 0
bytes_per_ts: int = 16
buffer_type
def cmd(self, *args) -> None:
50    def cmd(self, *args) -> None:
51        """
52        Execute spcm timestamp commands (see register 'SPC_TIMESTAMP_CMD' in chapter `Timestamp` in the manual)
53    
54        Parameters
55        ----------
56        *args : int
57            The different timestamp command flags to be executed.
58        """
59
60        cmd = 0
61        for arg in args:
62            cmd |= arg
63        self.card.set_i(SPC_TIMESTAMP_CMD, cmd)

Execute spcm timestamp commands (see register 'SPC_TIMESTAMP_CMD' in chapter Timestamp in the manual)

Parameters
  • *args (int): The different timestamp command flags to be executed.
def reset(self) -> None:
65    def reset(self) -> None:
66        """Reset the timestamp counter (see command 'SPC_TS_RESET' in chapter `Timestamp` in the manual)"""
67        self.cmd(SPC_TS_RESET)

Reset the timestamp counter (see command 'SPC_TS_RESET' in chapter Timestamp in the manual)

def mode(self, mode: int, *args: list[int]) -> None:
69    def mode(self, mode : int, *args : list[int]) -> None:
70        """
71        Set the mode of the timestamp counter (see register 'SPC_TIMESTAMP_CMD' in chapter `Timestamp` in the manual)
72
73        Parameters
74        ----------
75        mode : int
76            The mode of the timestamp counter
77        *args : list[int]
78            List of additional commands send with setting the mode
79        """
80        self.ts_mode = mode
81        self.cmd(self.ts_mode, *args)

Set the mode of the timestamp counter (see register 'SPC_TIMESTAMP_CMD' in chapter Timestamp in the manual)

Parameters
  • mode (int): The mode of the timestamp counter
  • *args (list[int]): List of additional commands send with setting the mode
def notify_timestamps(self, notify_timestamps: int) -> None:
83    def notify_timestamps(self, notify_timestamps : int) -> None:
84        """
85        Set the number of timestamps to notify the user about
86        
87        Parameters
88        ----------
89        notify_timestamps : int
90            the number of timestamps to notify the user about
91        """
92        self._notify_timestamps = notify_timestamps

Set the number of timestamps to notify the user about

Parameters
  • notify_timestamps (int): the number of timestamps to notify the user about
def allocate_buffer(self, num_timestamps: int) -> None:
 94    def allocate_buffer(self, num_timestamps : int) -> None:
 95        """
 96        Allocate the buffer for the timestamp data transfer
 97        
 98        Parameters
 99        ----------
100        num_timestamps : int
101            The number of timestamps to be allocated
102        """
103
104        num_timestamps = UnitConversion.convert(num_timestamps, units.S, int)
105        self.buffer_size = num_timestamps * self.bytes_per_ts
106
107        dwMask = self._buffer_alignment - 1
108
109        sample_type = np.int64
110        item_size = sample_type(0).itemsize
111        # allocate a buffer (numpy array) for DMA transfer: a little bigger one to have room for address alignment
112        databuffer_unaligned = np.zeros(((self._buffer_alignment + self.buffer_size) // item_size, ), dtype = sample_type)   # half byte count at int16 sample (// = integer division)
113        # two numpy-arrays may share the same memory: skip the begin up to the alignment boundary (ArrayVariable[SKIP_VALUE:])
114        # Address of data-memory from numpy-array: ArrayVariable.__array_interface__['data'][0]
115        start_pos_samples = ((self._buffer_alignment - (databuffer_unaligned.__array_interface__['data'][0] & dwMask)) // item_size)
116        self.buffer = databuffer_unaligned[start_pos_samples:start_pos_samples + (self.buffer_size // item_size)]   # byte address but int16 sample: therefore / 2
117        self.buffer = self.buffer.reshape((num_timestamps, 2), order='C') # array items per timestamp, because the maximum item size is 8 bytes = 64 bits

Allocate the buffer for the timestamp data transfer

Parameters
  • num_timestamps (int): The number of timestamps to be allocated
def start_buffer_transfer( self, *args, direction=1, notify_timestamps=None, transfer_offset=0, transfer_length=None) -> None:
119    def start_buffer_transfer(self, *args, direction=SPCM_DIR_CARDTOPC, notify_timestamps = None, transfer_offset=0, transfer_length=None) -> None:
120        """
121        Start the transfer of the timestamp data to the card
122        
123        Parameters
124        ----------
125        *args : list
126            list of additonal arguments that are added as flags to the start dma command
127        """
128        
129        notify_size = 0
130        if notify_timestamps is not None: 
131            self._notify_timestamps = notify_timestamps
132        if self._notify_timestamps: 
133            notify_size = self._notify_timestamps * self.bytes_per_ts
134
135        if transfer_offset:
136            transfer_offset_bytes = transfer_offset * self.bytes_per_ts
137        else:
138            transfer_offset_bytes = 0
139
140        if transfer_length is not None: 
141            transfer_length_bytes = transfer_length * self.bytes_per_ts
142        else:
143            transfer_length_bytes = self.buffer_size
144
145
146        # we define the buffer for transfer and start the DMA transfer
147        self.card._print("Starting the Timestamp transfer and waiting until data is in board memory")
148        self._c_buffer = self.buffer.ctypes.data_as(c_void_p)
149        spcm_dwDefTransfer_i64(self.card._handle, self.buffer_type, direction, notify_size, self._c_buffer, transfer_offset_bytes, transfer_length_bytes)
150        cmd = 0
151        for arg in args:
152            cmd |= arg
153        self.card.cmd(cmd)
154        self.card._print("... timestamp data transfer started")

Start the transfer of the timestamp data to the card

Parameters
  • *args (list): list of additonal arguments that are added as flags to the start dma command
def avail_card_len(self, num_timestamps: int) -> None:
156    def avail_card_len(self, num_timestamps : int) -> None:
157        """
158        Set the amount of timestamps that is available for reading of the timestamp buffer (see register 'SPC_TS_AVAIL_CARD_LEN' in chapter `Timestamp` in the manual)
159
160        Parameters
161        ----------
162        num_timestamps : int
163            the amount of timestamps that is available for reading
164        """
165        card_len = num_timestamps * self.bytes_per_ts
166        self.card.set_i(SPC_TS_AVAIL_CARD_LEN, card_len)

Set the amount of timestamps that is available for reading of the timestamp buffer (see register 'SPC_TS_AVAIL_CARD_LEN' in chapter Timestamp in the manual)

Parameters
  • num_timestamps (int): the amount of timestamps that is available for reading
def avail_user_pos(self) -> int:
168    def avail_user_pos(self) -> int:
169        """
170        Get the current position of the pointer in the timestamp buffer (see register 'SPC_TS_AVAIL_USER_POS' in chapter `Timestamp` in the manual)
171
172        Returns
173        -------
174        int
175            pointer position in timestamps
176        """
177        return self.card.get_i(SPC_TS_AVAIL_USER_POS) // self.bytes_per_ts

Get the current position of the pointer in the timestamp buffer (see register 'SPC_TS_AVAIL_USER_POS' in chapter Timestamp in the manual)

Returns
  • int: pointer position in timestamps
def avail_user_len(self) -> int:
179    def avail_user_len(self) -> int:
180        """
181        Get the current length of the data in timestamps in the timestamp buffer (see register 'SPC_TS_AVAIL_USER_LEN' in chapter `Timestamp` in the manual)
182
183        Returns
184        -------
185        int
186            data length available in number of timestamps
187        """
188        return self.card.get_i(SPC_TS_AVAIL_USER_LEN) // self.bytes_per_ts

Get the current length of the data in timestamps in the timestamp buffer (see register 'SPC_TS_AVAIL_USER_LEN' in chapter Timestamp in the manual)

Returns
  • int: data length available in number of timestamps
def to_transfer_timestamps(self, timestamps: int) -> None:
194    def to_transfer_timestamps(self, timestamps: int) -> None:
195        """
196        This method sets the number of timestamps to transfer
197
198        Parameters
199        ----------
200        timestamps : int
201            the number of timestamps to transfer
202        """
203        self._to_transfer_timestamps = timestamps

This method sets the number of timestamps to transfer

Parameters
  • timestamps (int): the number of timestamps to transfer
def poll( self, polling_length: int = 0) -> Union[numpy._typing._array_like._Buffer, numpy._typing._array_like._SupportsArray[numpy.dtype[Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[bool | int | float | complex | str | bytes]]:
205    def poll(self, polling_length : int = 0) -> npt.ArrayLike:
206        """
207        This method is called when polling for timestamps
208
209        Parameters
210        ----------
211        polling_length : int = 0
212            the number of timestamps to poll and wait for
213
214        Returns
215        -------
216        npt.ArrayLike
217            the next data block
218        """
219
220        while True:
221            user_pos = self.avail_user_pos()
222            user_len = self.avail_user_len()
223            if not polling_length and user_len >= 1:
224                self.avail_card_len(user_len)
225                return self.buffer[user_pos:user_pos+user_len, :]
226            elif user_len >= polling_length:
227                self.avail_card_len(polling_length)
228                return self.buffer[user_pos:user_pos+polling_length, :]

This method is called when polling for timestamps

Parameters
  • polling_length (int = 0): the number of timestamps to poll and wait for
Returns
  • npt.ArrayLike: the next data block
class Sequence(spcm.DataTransfer):
244class Sequence(DataTransfer):
245    """
246    a high-level class to control the sequence mode on Spectrum Instrumentation cards
247
248    For more information about what setups are available, please have a look at the user manual
249    for your specific card.
250
251    """
252
253    segments : list[Segment] = []
254    steps : list[Step] = []
255    _entry_step : Step = None
256    # _final_step : Step = None
257
258    def __init__(self, card, *args, **kwargs) -> None:
259        super().__init__(card, *args, **kwargs)
260        self.segments = []
261        self.steps = []
262        self.transitions = {}
263        self._entry_step = None
264        # self._final_step = None
265
266    def __str__(self) -> str:
267        return_string = ""
268        for i in range(len(self.steps)):
269            next_step_index, seqment_index, loops, flags = self.step_memory(i)
270            return_string += f"Step {i}: next {next_step_index}, segment {seqment_index}, loops {loops}, flags 0b{flags:b}\n"
271        return return_string
272    __repr__ = __str__
273
274    ### Low-level sequence control ###
275    def max_segments(self, max_segments : int = 0) -> int:
276        """
277        Set the maximum number of segments that can be used in the sequence mode (see register 'SPC_SEQMODE_MAXSEGMENTS' in chapter `Sequence Mode` in the manual)
278
279        Parameters
280        ----------
281        max_segments : int
282            The maximum number of segments that can be used in the sequence mode
283
284        Returns
285        -------
286        max_segments : int
287            The actual maximum number of segments that can be used in the sequence mode
288        """
289        if max_segments: 
290            self.card.set_i(SPC_SEQMODE_MAXSEGMENTS, max_segments)
291        return self.card.get_i(SPC_SEQMODE_MAXSEGMENTS)
292    
293    def write_segment(self, segment = None) -> int:
294        """
295        Defines the current segment to be addressed by the user. Must be programmed prior to changing any segment parameters. (see register 'SPC_SEQMODE_WRITESEGMENT' in chapter `Sequence Mode` in the manual)
296
297        Parameters
298        ----------
299        segment : int | Segment
300            The segment to be addresses
301
302        Returns
303        -------
304        segment : int
305            The segment to be addresses
306        """
307
308        if segment is not None:
309            if isinstance(segment, Segment):
310                segment = int(segment)
311            self.card.set_i(SPC_SEQMODE_WRITESEGMENT, segment)
312        return self.card.get_i(SPC_SEQMODE_WRITESEGMENT)
313    
314    def segment_size(self, segment_size : int = None, return_unit = None) -> int:
315        """
316        Defines the number of valid/to be replayed samples for the current selected memory segment in samples per channel. (see register 'SPC_SEQMODE_SEGMENTSIZE' in chapter `Sequence Mode` in the manual)
317
318        Parameters
319        ----------
320        segment_size : int | pint.Quantity
321            The size of the segment in samples
322
323        Returns
324        -------
325        segment_size : int
326            The size of the segment in samples
327        """
328
329        if segment_size is not None:
330            segment_size = UnitConversion.convert(segment_size, units.Sa, int)
331            self.card.set_i(SPC_SEQMODE_SEGMENTSIZE, segment_size)
332        return_value = self.card.get_i(SPC_SEQMODE_SEGMENTSIZE)
333        if return_unit is not None: return UnitConversion.to_unit(return_value, return_unit)
334        return return_value
335    
336    def step_memory(self, step_index : int, next_step_index : int = None, segment_index : int = None, loops : int = None, flags : int = None) -> tuple[int, int, int, int]:
337        """
338        Defines the step memory for the current selected memory segment. (see register 'SPC_SEQMODE_STEPMEM0' in chapter `Sequence Mode` in the manual)
339
340        Parameters
341        ----------
342        step_index : int
343            The index of the current step
344        next_step_index : int
345            The index of the next step in the sequence
346        segment_index : int
347            The index of the segment associated to the step
348        loops : int
349            The number of times the segment is looped 
350        flags : int
351            The flags for the step
352
353        Returns
354        -------
355        next_step_index : int
356            The index of the next step in the sequence
357        segment_index : int
358            The index of the segment associated to the step
359        loops : int
360            The number of times the segment is looped 
361        flags : int
362            The flags for the step
363
364        """
365        qwSequenceEntry = 0
366
367        # setup register value
368        if next_step_index is not None and segment_index is not None and loops is not None and flags is not None:
369            qwSequenceEntry = (flags & ~SPCSEQ_LOOPMASK) | (loops & SPCSEQ_LOOPMASK)
370            qwSequenceEntry <<= 32
371            qwSequenceEntry |= ((next_step_index << 16) & SPCSEQ_NEXTSTEPMASK) | (int(segment_index) & SPCSEQ_SEGMENTMASK)
372            self.card.set_i(SPC_SEQMODE_STEPMEM0 + step_index, qwSequenceEntry)
373        
374        qwSequenceEntry = self.card.get_i(SPC_SEQMODE_STEPMEM0 + step_index)
375        return (qwSequenceEntry & SPCSEQ_NEXTSTEPMASK) >> 16, qwSequenceEntry & SPCSEQ_SEGMENTMASK, (qwSequenceEntry >> 32) & SPCSEQ_LOOPMASK, (qwSequenceEntry >> 32) & ~SPCSEQ_LOOPMASK
376    
377    def start_step(self, start_step_index : int = None) -> int:
378        """
379        Defines which of all defined steps in the sequence memory will be used first directly after the card start. (see register 'SPC_SEQMODE_STARTSTEP' in chapter `Sequence Mode` in the manual)
380
381        Parameters
382        ----------
383        start_step_index : int
384            The index of the start step
385
386        Returns
387        -------
388        start_step_index : int
389            The index of the start step
390        """
391
392        if start_step_index is not None:
393            self.card.set_i(SPC_SEQMODE_STARTSTEP, start_step_index)
394        return self.card.get_i(SPC_SEQMODE_STARTSTEP)
395    
396    def status(self) -> int:
397        """
398        Reads the status of the sequence mode. (see register 'SPC_SEQMODE_STATUS' in chapter `Sequence Mode` in the manual)
399
400        Returns
401        -------
402        status : int
403            The status of the sequence mode
404        """
405
406        return self.card.get_i(SPC_SEQMODE_STATUS)
407    
408    def current_step(self) -> Step:
409        """
410        Returns the current step of the sequence mode
411
412        Returns
413        -------
414        step : Step
415            The current step of the sequence mode
416        """
417
418        current_step_index = self.status()
419        return self.steps[current_step_index] if current_step_index < len(self.steps) else None
420    
421    ### High-level Step and Segment handling ###
422    def add_segment(self, length : int = None) -> Segment:
423        """
424        Adds a segment to the sequence mode
425
426        Returns
427        -------
428        segment : Segment
429            The segment that was added
430        """
431
432        index = len(self.segments)
433        segment_array = self._allocate_buffer(length, False, self.num_channels)
434        segment = Segment(self.card, index, segment_array, length)
435        self.segments.append(segment)
436        return segment
437    
438    def add_step(self, segment : Segment, loops : int = 0) -> Step:
439        """
440        Adds a step to the sequence mode
441
442        Parameters
443        ----------
444        segment : Segment
445            The segment associated to the step
446        loops : int
447            The number of times the segment is looped
448
449        Returns
450        -------
451        step : Step
452            The step that was added
453        """
454
455        index = len(self.steps)
456        step = Step(self.card, index, segment = segment, loops = loops)
457        self.steps.append(step)
458        return step
459    
460    def entry_step(self, step : Step = None) -> Step:
461        """
462        Returns and sets the entry point of the step
463
464        Parameters
465        ----------
466        step : Step
467            The step that will be set as entry point
468
469        Returns
470        -------
471        Step
472            The step that is used a entry point
473        """
474
475        index = None
476        if step is not None:
477            self._entry_step = step
478            index = step.index()
479        index = self.start_step(index)
480        return self.steps[index]
481    
482    def write_setup(self):
483        """
484        Writes the setup to the card
485        """
486        
487        num_segments = len(self.segments)
488        num_segments_pow2 = np.power(2, np.ceil(np.log2(num_segments))).astype(np.int64)
489        self.max_segments(num_segments_pow2)
490
491        # write segments
492        for segment in self.segments:
493            segment.update()
494
495        # write steps
496        for step in self.steps:
497            step.update()
498        
499        self.card._print("Finished writing the sequence data to the card")
500        
501    def transfer_segment(self, segment : Segment) -> None:
502        """
503        Starts the buffer transfer for the given segment
504        """
505
506        segment.update()
507
508        # self.write_segment(segment)
509        # self.segment_size(len(segment))
510
511        # transfer_offset_bytes = 0
512        # transfer_length_bytes = segment.nbytes
513
514        # buffer_type = SPCM_BUF_DATA
515        # direction = SPCM_DIR_PCTOCARD
516        
517        # # we define the buffer for transfer
518        # self.card._print("Starting the DMA transfer and waiting until data is in board memory")
519        # _c_buffer = segment.ctypes.data_as(c_void_p)
520        # self.card._check_error(spcm_dwDefTransfer_i64(self.card._handle, buffer_type, direction, self.notify_size, _c_buffer, transfer_offset_bytes, transfer_length_bytes))
521
522        # self.card.cmd(M2CMD_DATA_STARTDMA, M2CMD_DATA_WAITDMA)

a high-level class to control the sequence mode on Spectrum Instrumentation cards

For more information about what setups are available, please have a look at the user manual for your specific card.

Sequence(card, *args, **kwargs)
258    def __init__(self, card, *args, **kwargs) -> None:
259        super().__init__(card, *args, **kwargs)
260        self.segments = []
261        self.steps = []
262        self.transitions = {}
263        self._entry_step = None
264        # self._final_step = None

Initialize the DataTransfer object with a card object and additional arguments

Parameters
  • card (Card): the card object that is used for the data transfer
  • *args (list): list of additional arguments
  • **kwargs (dict): dictionary of additional keyword arguments
segments: list[spcm.classes_sequence.Segment] = []
steps: list[spcm.classes_sequence.Step] = []
transitions
def max_segments(self, max_segments: int = 0) -> int:
275    def max_segments(self, max_segments : int = 0) -> int:
276        """
277        Set the maximum number of segments that can be used in the sequence mode (see register 'SPC_SEQMODE_MAXSEGMENTS' in chapter `Sequence Mode` in the manual)
278
279        Parameters
280        ----------
281        max_segments : int
282            The maximum number of segments that can be used in the sequence mode
283
284        Returns
285        -------
286        max_segments : int
287            The actual maximum number of segments that can be used in the sequence mode
288        """
289        if max_segments: 
290            self.card.set_i(SPC_SEQMODE_MAXSEGMENTS, max_segments)
291        return self.card.get_i(SPC_SEQMODE_MAXSEGMENTS)

Set the maximum number of segments that can be used in the sequence mode (see register 'SPC_SEQMODE_MAXSEGMENTS' in chapter Sequence Mode in the manual)

Parameters
  • max_segments (int): The maximum number of segments that can be used in the sequence mode
Returns
  • max_segments (int): The actual maximum number of segments that can be used in the sequence mode
def write_segment(self, segment=None) -> int:
293    def write_segment(self, segment = None) -> int:
294        """
295        Defines the current segment to be addressed by the user. Must be programmed prior to changing any segment parameters. (see register 'SPC_SEQMODE_WRITESEGMENT' in chapter `Sequence Mode` in the manual)
296
297        Parameters
298        ----------
299        segment : int | Segment
300            The segment to be addresses
301
302        Returns
303        -------
304        segment : int
305            The segment to be addresses
306        """
307
308        if segment is not None:
309            if isinstance(segment, Segment):
310                segment = int(segment)
311            self.card.set_i(SPC_SEQMODE_WRITESEGMENT, segment)
312        return self.card.get_i(SPC_SEQMODE_WRITESEGMENT)

Defines the current segment to be addressed by the user. Must be programmed prior to changing any segment parameters. (see register 'SPC_SEQMODE_WRITESEGMENT' in chapter Sequence Mode in the manual)

Parameters
  • segment (int | Segment): The segment to be addresses
Returns
  • segment (int): The segment to be addresses
def segment_size(self, segment_size: int = None, return_unit=None) -> int:
314    def segment_size(self, segment_size : int = None, return_unit = None) -> int:
315        """
316        Defines the number of valid/to be replayed samples for the current selected memory segment in samples per channel. (see register 'SPC_SEQMODE_SEGMENTSIZE' in chapter `Sequence Mode` in the manual)
317
318        Parameters
319        ----------
320        segment_size : int | pint.Quantity
321            The size of the segment in samples
322
323        Returns
324        -------
325        segment_size : int
326            The size of the segment in samples
327        """
328
329        if segment_size is not None:
330            segment_size = UnitConversion.convert(segment_size, units.Sa, int)
331            self.card.set_i(SPC_SEQMODE_SEGMENTSIZE, segment_size)
332        return_value = self.card.get_i(SPC_SEQMODE_SEGMENTSIZE)
333        if return_unit is not None: return UnitConversion.to_unit(return_value, return_unit)
334        return return_value

Defines the number of valid/to be replayed samples for the current selected memory segment in samples per channel. (see register 'SPC_SEQMODE_SEGMENTSIZE' in chapter Sequence Mode in the manual)

Parameters
  • segment_size (int | pint.Quantity): The size of the segment in samples
Returns
  • segment_size (int): The size of the segment in samples
def step_memory( self, step_index: int, next_step_index: int = None, segment_index: int = None, loops: int = None, flags: int = None) -> tuple[int, int, int, int]:
336    def step_memory(self, step_index : int, next_step_index : int = None, segment_index : int = None, loops : int = None, flags : int = None) -> tuple[int, int, int, int]:
337        """
338        Defines the step memory for the current selected memory segment. (see register 'SPC_SEQMODE_STEPMEM0' in chapter `Sequence Mode` in the manual)
339
340        Parameters
341        ----------
342        step_index : int
343            The index of the current step
344        next_step_index : int
345            The index of the next step in the sequence
346        segment_index : int
347            The index of the segment associated to the step
348        loops : int
349            The number of times the segment is looped 
350        flags : int
351            The flags for the step
352
353        Returns
354        -------
355        next_step_index : int
356            The index of the next step in the sequence
357        segment_index : int
358            The index of the segment associated to the step
359        loops : int
360            The number of times the segment is looped 
361        flags : int
362            The flags for the step
363
364        """
365        qwSequenceEntry = 0
366
367        # setup register value
368        if next_step_index is not None and segment_index is not None and loops is not None and flags is not None:
369            qwSequenceEntry = (flags & ~SPCSEQ_LOOPMASK) | (loops & SPCSEQ_LOOPMASK)
370            qwSequenceEntry <<= 32
371            qwSequenceEntry |= ((next_step_index << 16) & SPCSEQ_NEXTSTEPMASK) | (int(segment_index) & SPCSEQ_SEGMENTMASK)
372            self.card.set_i(SPC_SEQMODE_STEPMEM0 + step_index, qwSequenceEntry)
373        
374        qwSequenceEntry = self.card.get_i(SPC_SEQMODE_STEPMEM0 + step_index)
375        return (qwSequenceEntry & SPCSEQ_NEXTSTEPMASK) >> 16, qwSequenceEntry & SPCSEQ_SEGMENTMASK, (qwSequenceEntry >> 32) & SPCSEQ_LOOPMASK, (qwSequenceEntry >> 32) & ~SPCSEQ_LOOPMASK

Defines the step memory for the current selected memory segment. (see register 'SPC_SEQMODE_STEPMEM0' in chapter Sequence Mode in the manual)

Parameters
  • step_index (int): The index of the current step
  • next_step_index (int): The index of the next step in the sequence
  • segment_index (int): The index of the segment associated to the step
  • loops (int): The number of times the segment is looped
  • flags (int): The flags for the step
Returns
  • next_step_index (int): The index of the next step in the sequence
  • segment_index (int): The index of the segment associated to the step
  • loops (int): The number of times the segment is looped
  • flags (int): The flags for the step
def start_step(self, start_step_index: int = None) -> int:
377    def start_step(self, start_step_index : int = None) -> int:
378        """
379        Defines which of all defined steps in the sequence memory will be used first directly after the card start. (see register 'SPC_SEQMODE_STARTSTEP' in chapter `Sequence Mode` in the manual)
380
381        Parameters
382        ----------
383        start_step_index : int
384            The index of the start step
385
386        Returns
387        -------
388        start_step_index : int
389            The index of the start step
390        """
391
392        if start_step_index is not None:
393            self.card.set_i(SPC_SEQMODE_STARTSTEP, start_step_index)
394        return self.card.get_i(SPC_SEQMODE_STARTSTEP)

Defines which of all defined steps in the sequence memory will be used first directly after the card start. (see register 'SPC_SEQMODE_STARTSTEP' in chapter Sequence Mode in the manual)

Parameters
  • start_step_index (int): The index of the start step
Returns
  • start_step_index (int): The index of the start step
def status(self) -> int:
396    def status(self) -> int:
397        """
398        Reads the status of the sequence mode. (see register 'SPC_SEQMODE_STATUS' in chapter `Sequence Mode` in the manual)
399
400        Returns
401        -------
402        status : int
403            The status of the sequence mode
404        """
405
406        return self.card.get_i(SPC_SEQMODE_STATUS)

Reads the status of the sequence mode. (see register 'SPC_SEQMODE_STATUS' in chapter Sequence Mode in the manual)

Returns
  • status (int): The status of the sequence mode
def current_step(self) -> spcm.classes_sequence.Step:
408    def current_step(self) -> Step:
409        """
410        Returns the current step of the sequence mode
411
412        Returns
413        -------
414        step : Step
415            The current step of the sequence mode
416        """
417
418        current_step_index = self.status()
419        return self.steps[current_step_index] if current_step_index < len(self.steps) else None

Returns the current step of the sequence mode

Returns
  • step (Step): The current step of the sequence mode
def add_segment(self, length: int = None) -> spcm.classes_sequence.Segment:
422    def add_segment(self, length : int = None) -> Segment:
423        """
424        Adds a segment to the sequence mode
425
426        Returns
427        -------
428        segment : Segment
429            The segment that was added
430        """
431
432        index = len(self.segments)
433        segment_array = self._allocate_buffer(length, False, self.num_channels)
434        segment = Segment(self.card, index, segment_array, length)
435        self.segments.append(segment)
436        return segment

Adds a segment to the sequence mode

Returns
  • segment (Segment): The segment that was added
def add_step( self, segment: spcm.classes_sequence.Segment, loops: int = 0) -> spcm.classes_sequence.Step:
438    def add_step(self, segment : Segment, loops : int = 0) -> Step:
439        """
440        Adds a step to the sequence mode
441
442        Parameters
443        ----------
444        segment : Segment
445            The segment associated to the step
446        loops : int
447            The number of times the segment is looped
448
449        Returns
450        -------
451        step : Step
452            The step that was added
453        """
454
455        index = len(self.steps)
456        step = Step(self.card, index, segment = segment, loops = loops)
457        self.steps.append(step)
458        return step

Adds a step to the sequence mode

Parameters
  • segment (Segment): The segment associated to the step
  • loops (int): The number of times the segment is looped
Returns
  • step (Step): The step that was added
def entry_step( self, step: spcm.classes_sequence.Step = None) -> spcm.classes_sequence.Step:
460    def entry_step(self, step : Step = None) -> Step:
461        """
462        Returns and sets the entry point of the step
463
464        Parameters
465        ----------
466        step : Step
467            The step that will be set as entry point
468
469        Returns
470        -------
471        Step
472            The step that is used a entry point
473        """
474
475        index = None
476        if step is not None:
477            self._entry_step = step
478            index = step.index()
479        index = self.start_step(index)
480        return self.steps[index]

Returns and sets the entry point of the step

Parameters
  • step (Step): The step that will be set as entry point
Returns
  • Step: The step that is used a entry point
def write_setup(self):
482    def write_setup(self):
483        """
484        Writes the setup to the card
485        """
486        
487        num_segments = len(self.segments)
488        num_segments_pow2 = np.power(2, np.ceil(np.log2(num_segments))).astype(np.int64)
489        self.max_segments(num_segments_pow2)
490
491        # write segments
492        for segment in self.segments:
493            segment.update()
494
495        # write steps
496        for step in self.steps:
497            step.update()
498        
499        self.card._print("Finished writing the sequence data to the card")

Writes the setup to the card

def transfer_segment(self, segment: spcm.classes_sequence.Segment) -> None:
501    def transfer_segment(self, segment : Segment) -> None:
502        """
503        Starts the buffer transfer for the given segment
504        """
505
506        segment.update()
507
508        # self.write_segment(segment)
509        # self.segment_size(len(segment))
510
511        # transfer_offset_bytes = 0
512        # transfer_length_bytes = segment.nbytes
513
514        # buffer_type = SPCM_BUF_DATA
515        # direction = SPCM_DIR_PCTOCARD
516        
517        # # we define the buffer for transfer
518        # self.card._print("Starting the DMA transfer and waiting until data is in board memory")
519        # _c_buffer = segment.ctypes.data_as(c_void_p)
520        # self.card._check_error(spcm_dwDefTransfer_i64(self.card._handle, buffer_type, direction, self.notify_size, _c_buffer, transfer_offset_bytes, transfer_length_bytes))
521
522        # self.card.cmd(M2CMD_DATA_STARTDMA, M2CMD_DATA_WAITDMA)

Starts the buffer transfer for the given segment

class ABA(spcm.DataTransfer):
12class ABA(DataTransfer):
13    """a high-level class to control ABA functionality on Spectrum Instrumentation cards
14
15    For more information about what setups are available, please have a look at the user manual
16    for your specific card.
17
18    """
19
20    # Private
21    _divider : int = 1
22
23    def __init__(self, card, *args, **kwargs) -> None:
24        super().__init__(card, *args, **kwargs)
25        self.buffer_type = SPCM_BUF_ABA
26        self._divider = 1
27    
28    def divider(self, divider : int = None) -> int:
29        """
30        Set the divider for the ABA functionality (see register `SPC_ABADIVIDER` in the manual)
31
32        Parameters
33        ----------
34        divider : int
35            The divider for the ABA functionality
36
37        Returns
38        -------
39        int
40            The divider for the ABA functionality
41
42        """
43        
44        if divider is not None:
45            self.card.set_i(SPC_ABADIVIDER, divider)
46        self._divider = self.card.get_i(SPC_ABADIVIDER)
47        return self._divider
48    
49    def _sample_rate(self) -> pint.Quantity:
50        """
51        Get the sample rate of the ABA data of the card
52
53        Returns
54        -------
55        pint.Quantity
56            the sample rate of the card in Hz as a pint quantity
57        """
58        
59        sample_rate = super()._sample_rate()
60        return sample_rate / self._divider
61
62    def start_buffer_transfer(self, *args, buffer_type=SPCM_BUF_ABA, **kwargs) -> int:
63        return super().start_buffer_transfer(*args, buffer_type=buffer_type, **kwargs)

a high-level class to control ABA functionality on Spectrum Instrumentation cards

For more information about what setups are available, please have a look at the user manual for your specific card.

ABA(card, *args, **kwargs)
23    def __init__(self, card, *args, **kwargs) -> None:
24        super().__init__(card, *args, **kwargs)
25        self.buffer_type = SPCM_BUF_ABA
26        self._divider = 1

Initialize the DataTransfer object with a card object and additional arguments

Parameters
  • card (Card): the card object that is used for the data transfer
  • *args (list): list of additional arguments
  • **kwargs (dict): dictionary of additional keyword arguments
buffer_type
def divider(self, divider: int = None) -> int:
28    def divider(self, divider : int = None) -> int:
29        """
30        Set the divider for the ABA functionality (see register `SPC_ABADIVIDER` in the manual)
31
32        Parameters
33        ----------
34        divider : int
35            The divider for the ABA functionality
36
37        Returns
38        -------
39        int
40            The divider for the ABA functionality
41
42        """
43        
44        if divider is not None:
45            self.card.set_i(SPC_ABADIVIDER, divider)
46        self._divider = self.card.get_i(SPC_ABADIVIDER)
47        return self._divider

Set the divider for the ABA functionality (see register SPC_ABADIVIDER in the manual)

Parameters
  • divider (int): The divider for the ABA functionality
Returns
  • int: The divider for the ABA functionality
def start_buffer_transfer(self, *args, buffer_type=2000, **kwargs) -> int:
62    def start_buffer_transfer(self, *args, buffer_type=SPCM_BUF_ABA, **kwargs) -> int:
63        return super().start_buffer_transfer(*args, buffer_type=buffer_type, **kwargs)

Start the transfer of the data to or from the card (see the API function spcm_dwDefTransfer_i64 in the manual)

Parameters
  • *args (list): list of additonal arguments that are added as flags to the start dma command
  • buffer_type (int): the type of buffer that is used for the transfer
  • direction (int): the direction of the transfer
  • notify_samples (int): the number of samples to notify the user about
  • transfer_offset (int): the offset of the transfer
  • transfer_length (int): the length of the transfer
  • exception_num_samples (bool): if True, an exception is raised if the number of samples is not a multiple of the notify samples. The automatic buffer handling only works with the number of samples being a multiple of the notify samples.
Raises
  • SpcmException
class BlockAverage(spcm.Multi):
11class BlockAverage(Multi):
12    """a high-level class to control Block Average functionality on Spectrum Instrumentation cards
13
14    For more information about what setups are available, please have a look at the user manual
15    for your specific card.
16
17    """
18
19    def __init__(self, card, *args, **kwargs) -> None:
20        super().__init__(card, *args, **kwargs)
21    
22    def averages(self, num_averages : int = None) -> int:
23        """Sets the number of averages for the block averaging functionality (see hardware reference manual register 'SPC_AVERAGES')
24
25        Parameters
26        ----------
27        num_averages : int
28            the number of averages for the boxcar functionality
29
30        Returns
31        -------
32        int
33            the number of averages for the block averaging functionality
34        """
35        if num_averages is not None:
36            self.card.set_i(SPC_AVERAGES, num_averages)
37        return self.card.get_i(SPC_AVERAGES)
38    
39    def _bits_per_sample(self) -> int:
40        """
41        Get the number of bits per sample
42
43        Returns
44        -------
45        int
46            number of bits per sample
47        """
48        self.bits_per_sample = super()._bits_per_sample() * 2
49        return self.bits_per_sample
50    
51    def _bytes_per_sample(self) -> int:
52        """
53        Get the number of bytes per sample
54
55        Returns
56        -------
57        int
58            number of bytes per sample
59        """
60        self.bytes_per_sample = super()._bytes_per_sample() * 2
61        return self.bytes_per_sample

a high-level class to control Block Average functionality on Spectrum Instrumentation cards

For more information about what setups are available, please have a look at the user manual for your specific card.

BlockAverage(card, *args, **kwargs)
19    def __init__(self, card, *args, **kwargs) -> None:
20        super().__init__(card, *args, **kwargs)

Initialize the DataTransfer object with a card object and additional arguments

Parameters
  • card (Card): the card object that is used for the data transfer
  • *args (list): list of additional arguments
  • **kwargs (dict): dictionary of additional keyword arguments
def averages(self, num_averages: int = None) -> int:
22    def averages(self, num_averages : int = None) -> int:
23        """Sets the number of averages for the block averaging functionality (see hardware reference manual register 'SPC_AVERAGES')
24
25        Parameters
26        ----------
27        num_averages : int
28            the number of averages for the boxcar functionality
29
30        Returns
31        -------
32        int
33            the number of averages for the block averaging functionality
34        """
35        if num_averages is not None:
36            self.card.set_i(SPC_AVERAGES, num_averages)
37        return self.card.get_i(SPC_AVERAGES)

Sets the number of averages for the block averaging functionality (see hardware reference manual register 'SPC_AVERAGES')

Parameters
  • num_averages (int): the number of averages for the boxcar functionality
Returns
  • int: the number of averages for the block averaging functionality
class Boxcar(spcm.Multi):
11class Boxcar(Multi):
12    """a high-level class to control Boxcar functionality on Spectrum Instrumentation cards
13
14    For more information about what setups are available, please have a look at the user manual
15    for your specific card.
16
17    """
18
19    def __init__(self, card, *args, **kwargs) -> None:
20        super().__init__(card, *args, **kwargs)
21    
22    def box_averages(self, num_averages : int = None) -> int:
23        """Sets the number of averages for the boxcar functionality (see hardware reference manual register 'SPC_BOX_AVERAGES')
24
25        Parameters
26        ----------
27        num_averages : int
28            the number of averages for the boxcar functionality
29
30        Returns
31        -------
32        int
33            the number of averages for the boxcar functionality
34        """
35        if num_averages is not None:
36            self.card.set_i(SPC_BOX_AVERAGES, num_averages)
37        return self.card.get_i(SPC_BOX_AVERAGES)
38    
39    def _bits_per_sample(self) -> int:
40        """
41        Get the number of bits per sample
42
43        Returns
44        -------
45        int
46            number of bits per sample
47        """
48        self.bits_per_sample = 32
49        return self.bits_per_sample
50    
51    def _bytes_per_sample(self) -> int:
52        """
53        Get the number of bytes per sample
54
55        Returns
56        -------
57        int
58            number of bytes per sample
59        """
60        self.bytes_per_sample = 4
61        return self.bytes_per_sample
62
63    def numpy_type(self) -> npt.NDArray[np.int_]:
64        """
65        Get the type of numpy data from number of bytes
66
67        Returns
68        -------
69        numpy data type
70            the type of data that is used by the card
71        """
72        return np.int32

a high-level class to control Boxcar functionality on Spectrum Instrumentation cards

For more information about what setups are available, please have a look at the user manual for your specific card.

Boxcar(card, *args, **kwargs)
19    def __init__(self, card, *args, **kwargs) -> None:
20        super().__init__(card, *args, **kwargs)

Initialize the DataTransfer object with a card object and additional arguments

Parameters
  • card (Card): the card object that is used for the data transfer
  • *args (list): list of additional arguments
  • **kwargs (dict): dictionary of additional keyword arguments
def box_averages(self, num_averages: int = None) -> int:
22    def box_averages(self, num_averages : int = None) -> int:
23        """Sets the number of averages for the boxcar functionality (see hardware reference manual register 'SPC_BOX_AVERAGES')
24
25        Parameters
26        ----------
27        num_averages : int
28            the number of averages for the boxcar functionality
29
30        Returns
31        -------
32        int
33            the number of averages for the boxcar functionality
34        """
35        if num_averages is not None:
36            self.card.set_i(SPC_BOX_AVERAGES, num_averages)
37        return self.card.get_i(SPC_BOX_AVERAGES)

Sets the number of averages for the boxcar functionality (see hardware reference manual register 'SPC_BOX_AVERAGES')

Parameters
  • num_averages (int): the number of averages for the boxcar functionality
Returns
  • int: the number of averages for the boxcar functionality
def numpy_type(self) -> numpy.ndarray[tuple[int, ...], numpy.dtype[numpy.int64]]:
63    def numpy_type(self) -> npt.NDArray[np.int_]:
64        """
65        Get the type of numpy data from number of bytes
66
67        Returns
68        -------
69        numpy data type
70            the type of data that is used by the card
71        """
72        return np.int32

Get the type of numpy data from number of bytes

Returns
  • numpy data type: the type of data that is used by the card
class BlockStatistics(spcm.Multi):
26class BlockStatistics(Multi):
27    """a high-level class to control Block Statistics functionality on Spectrum Instrumentation cards
28
29    For more information about what setups are available, please have a look at the user manual
30    for your specific card.
31
32    """
33
34    def __init__(self, card, *args, **kwargs) -> None:
35        super().__init__(card, *args, **kwargs)
36    
37    def _bits_per_sample(self) -> int:
38        """
39        Get the number of bits per sample
40
41        Returns
42        -------
43        int
44            number of bits per sample
45        """
46        self.bits_per_sample = SPCM_SEGSTAT_STRUCT_CHx.itemsize * 8
47        return self.bits_per_sample
48    
49    def _bytes_per_sample(self) -> int:
50        """
51        Get the number of bytes per sample
52
53        Returns
54        -------
55        int
56            number of bytes per sample
57        """
58        self.bytes_per_sample = SPCM_SEGSTAT_STRUCT_CHx.itemsize
59        return self.bytes_per_sample
60
61    def allocate_buffer(self, segment_samples : int, num_segments : int = None) -> None:
62        """
63        Memory allocation for the buffer that is used for communicating with the card
64
65        Parameters
66        ----------
67        segment_samples : int | pint.Quantity
68            use the number of samples and get the number of active channels and bytes per samples directly from the card
69        num_segments : int = None
70            the number of segments that are used for the multiple recording mode
71        """
72
73        segment_samples = UnitConversion.convert(segment_samples, units.S, int)
74        num_segments = UnitConversion.convert(num_segments, units.S, int)
75
76        self.buffer_samples = num_segments # There is always just one sample (statistics block) per segment
77        self._num_segments = num_segments
78        self.segment_samples(segment_samples)
79        
80        num_channels = self.card.active_channels()
81        self.buffer = np.empty((self._num_segments, num_channels), dtype=self.numpy_type())
82
83    def numpy_type(self) -> npt.NDArray[np.int_]:
84        """
85        Get the type of numpy data from number of bytes
86
87        Returns
88        -------
89        numpy data type
90            the type of data that is used by the card
91        """
92        
93        return SPCM_SEGSTAT_STRUCT_CHx

a high-level class to control Block Statistics functionality on Spectrum Instrumentation cards

For more information about what setups are available, please have a look at the user manual for your specific card.

BlockStatistics(card, *args, **kwargs)
34    def __init__(self, card, *args, **kwargs) -> None:
35        super().__init__(card, *args, **kwargs)

Initialize the DataTransfer object with a card object and additional arguments

Parameters
  • card (Card): the card object that is used for the data transfer
  • *args (list): list of additional arguments
  • **kwargs (dict): dictionary of additional keyword arguments
def allocate_buffer(self, segment_samples: int, num_segments: int = None) -> None:
61    def allocate_buffer(self, segment_samples : int, num_segments : int = None) -> None:
62        """
63        Memory allocation for the buffer that is used for communicating with the card
64
65        Parameters
66        ----------
67        segment_samples : int | pint.Quantity
68            use the number of samples and get the number of active channels and bytes per samples directly from the card
69        num_segments : int = None
70            the number of segments that are used for the multiple recording mode
71        """
72
73        segment_samples = UnitConversion.convert(segment_samples, units.S, int)
74        num_segments = UnitConversion.convert(num_segments, units.S, int)
75
76        self.buffer_samples = num_segments # There is always just one sample (statistics block) per segment
77        self._num_segments = num_segments
78        self.segment_samples(segment_samples)
79        
80        num_channels = self.card.active_channels()
81        self.buffer = np.empty((self._num_segments, num_channels), dtype=self.numpy_type())

Memory allocation for the buffer that is used for communicating with the card

Parameters
  • segment_samples (int | pint.Quantity): use the number of samples and get the number of active channels and bytes per samples directly from the card
  • num_segments (int = None): the number of segments that are used for the multiple recording mode
def numpy_type(self) -> numpy.ndarray[tuple[int, ...], numpy.dtype[numpy.int64]]:
83    def numpy_type(self) -> npt.NDArray[np.int_]:
84        """
85        Get the type of numpy data from number of bytes
86
87        Returns
88        -------
89        numpy data type
90            the type of data that is used by the card
91        """
92        
93        return SPCM_SEGSTAT_STRUCT_CHx

Get the type of numpy data from number of bytes

Returns
  • numpy data type: the type of data that is used by the card
class SpcmException(builtins.Exception):
100class SpcmException(Exception):
101    """a container class for handling driver level errors
102
103    Examples
104    ----------
105    ```python
106    raise SpcmException(handle=card.handle())
107    raise SpcmException(register=0, value=0, text="Some weird error")
108    ```
109    
110    Parameters
111    ---------
112    error : SpcmError
113        the error that induced the raising of the exception
114    
115    """
116    error = None
117
118    def __init__(self, error = None, register = None, value = None, text = None) -> None:
119        """
120        Constructs exception object and an associated error object, either by getting 
121        the last error from the card specified by the handle or using the information 
122        coming from the parameters register, value and text
123
124        Parameters
125        ----------
126        handle : drv_handle (optional)
127            a card handle to obtain the last error
128        register, value and text : int, int, str (optional)
129            parameters to define an error that is not raised by a driver error
130        """
131        if error: self.error = error
132        if register or value or text:
133            self.error = SpcmError(register=register, value=value, text=text)
134        
135    
136    def __str__(self) -> str:
137        """
138        Returns a human-readable text of the last error connected to the exception
139    
140        Class Parameters
141        ----------
142        self.error
143    
144        Returns
145        -------
146        str
147            the human-readable text as return by the error
148        """
149        
150        return str(self.error)

a container class for handling driver level errors

Examples
raise SpcmException(handle=card.handle())
raise SpcmException(register=0, value=0, text="Some weird error")
Parameters
  • error (SpcmError): the error that induced the raising of the exception
SpcmException(error=None, register=None, value=None, text=None)
118    def __init__(self, error = None, register = None, value = None, text = None) -> None:
119        """
120        Constructs exception object and an associated error object, either by getting 
121        the last error from the card specified by the handle or using the information 
122        coming from the parameters register, value and text
123
124        Parameters
125        ----------
126        handle : drv_handle (optional)
127            a card handle to obtain the last error
128        register, value and text : int, int, str (optional)
129            parameters to define an error that is not raised by a driver error
130        """
131        if error: self.error = error
132        if register or value or text:
133            self.error = SpcmError(register=register, value=value, text=text)

Constructs exception object and an associated error object, either by getting the last error from the card specified by the handle or using the information coming from the parameters register, value and text

Parameters
  • handle (drv_handle (optional)): a card handle to obtain the last error
  • register, value and text (int, int, str (optional)): parameters to define an error that is not raised by a driver error
error = None
class SpcmTimeout(builtins.Exception):
152class SpcmTimeout(Exception):
153    """a container class for handling specific timeout exceptions"""
154    pass

a container class for handling specific timeout exceptions

class SpcmDeviceNotFound(spcm.SpcmException):
156class SpcmDeviceNotFound(SpcmException):
157    """a container class for handling specific device not found exceptions"""
158    pass

a container class for handling specific device not found exceptions

class SpcmError:
10class SpcmError():
11    """a container class for handling driver level errors
12        
13    Examples
14    ----------
15    ```python
16    error = SpcmError(handle=card.handle())
17    error = SpcmError(register=0, value=0, text="Some weird error")
18    ```
19
20    Parameters
21    ---------
22    register : int
23        the register address that triggered the error
24    
25    value : int
26        the value that was written to the register address
27    
28    text : str
29        the human-readable text associated with the error
30    
31    """
32
33    register : int = 0
34    value : int = 0
35    text : str = ""
36    _handle = None
37    
38    def __init__(self, handle = None, register = None, value = None, text = None) -> None:
39        """
40        Constructs an error object, either by getting the last error from the card specified by the handle
41        or using the information coming from the parameters register, value and text
42
43        Parameters
44        ----------
45        handle : pyspcm.drv_handle (optional)
46            a card handle to obtain the last error
47        register, value and text : int, int, str (optional)
48            parameters to define an error that is not raised by a driver error
49        """
50        if handle:
51            self._handle = handle
52            self.get_info()
53        if register: self.register = register
54        if value: self.value = value
55        if text: self.text = text
56
57    def get_info(self) -> int:
58        """
59        Gets the last error registered by the card and puts it in the object
60    
61        Class Parameters
62        ----------
63        self.register
64        self.value
65        self.text
66    
67        Returns
68        -------
69        int
70            Error number of the spcm_dwGetErrorInfo_i32 class
71        """
72
73        register = uint32(0)
74        value = int32(0)
75        text = create_string_buffer(ERRORTEXTLEN)
76        dwErr = spcm_dwGetErrorInfo_i32(self._handle, byref(register), byref(value), byref(text))
77        self.register = register.value
78        self.value = value.value
79        self.text = text.value.decode('utf-8')
80        return dwErr
81    
82    def __str__(self) -> str:
83        """
84        Returns a human-readable text of the last error
85    
86        Class Parameters
87        ----------
88        self.register
89        self.value
90        self.text
91    
92        Returns
93        -------
94        str
95            the human-readable text as saved in self.text.
96        """
97        
98        return str(self.text)

a container class for handling driver level errors

Examples
error = SpcmError(handle=card.handle())
error = SpcmError(register=0, value=0, text="Some weird error")
Parameters
  • register (int): the register address that triggered the error
  • value (int): the value that was written to the register address
  • text (str): the human-readable text associated with the error
SpcmError(handle=None, register=None, value=None, text=None)
38    def __init__(self, handle = None, register = None, value = None, text = None) -> None:
39        """
40        Constructs an error object, either by getting the last error from the card specified by the handle
41        or using the information coming from the parameters register, value and text
42
43        Parameters
44        ----------
45        handle : pyspcm.drv_handle (optional)
46            a card handle to obtain the last error
47        register, value and text : int, int, str (optional)
48            parameters to define an error that is not raised by a driver error
49        """
50        if handle:
51            self._handle = handle
52            self.get_info()
53        if register: self.register = register
54        if value: self.value = value
55        if text: self.text = text

Constructs an error object, either by getting the last error from the card specified by the handle or using the information coming from the parameters register, value and text

Parameters
  • handle (pyspcm.drv_handle (optional)): a card handle to obtain the last error
  • register, value and text (int, int, str (optional)): parameters to define an error that is not raised by a driver error
register: int = 0
value: int = 0
text: str = ''
def get_info(self) -> int:
57    def get_info(self) -> int:
58        """
59        Gets the last error registered by the card and puts it in the object
60    
61        Class Parameters
62        ----------
63        self.register
64        self.value
65        self.text
66    
67        Returns
68        -------
69        int
70            Error number of the spcm_dwGetErrorInfo_i32 class
71        """
72
73        register = uint32(0)
74        value = int32(0)
75        text = create_string_buffer(ERRORTEXTLEN)
76        dwErr = spcm_dwGetErrorInfo_i32(self._handle, byref(register), byref(value), byref(text))
77        self.register = register.value
78        self.value = value.value
79        self.text = text.value.decode('utf-8')
80        return dwErr

Gets the last error registered by the card and puts it in the object

Class Parameters

self.register self.value self.text

Returns
  • int: Error number of the spcm_dwGetErrorInfo_i32 class
class SCAPPTransfer(spcm.DataTransfer):
 60class SCAPPTransfer(DataTransfer):
 61    """
 62    Class for data transfer between the card and the host using the SCAPP API.
 63    """
 64
 65    direction : Direction = None
 66
 67    def __init__(self, card : Card, direction : Direction = Direction.Acquisition):
 68        if not _cuda_support:
 69            raise ImportError("CUDA support is not available. Please install the cupy and cuda-python packages.")
 70        super().__init__(card)
 71        scapp_feature = bool(self.card._features & SPCM_FEAT_SCAPP)
 72        if not scapp_feature:
 73            raise SpcmException(text="The card does not have the SCAPP option installed. SCAPP is a add-on feature that needs to be bought separately, please contact info@spec.de and ask for the SCAPP option for the card with serial number {}".format(self.card.sn()))
 74        self.direction = direction
 75        self.iterator_index = 0
 76
 77    def allocate_buffer(self, num_samples : int) -> None:
 78        """
 79        Memory allocation for the buffer that is used for communicating with the card
 80
 81        Parameters
 82        ----------
 83        num_samples : int | pint.Quantity = None
 84            use the number of samples an get the number of active channels and bytes per samples directly from the card
 85        """
 86        
 87        self.buffer_samples = UnitConversion.convert(num_samples, units.Sa, int)
 88        # Allocate RDMA buffer
 89        self.buffer = cp.empty((self.num_channels, self.buffer_samples), dtype = self.numpy_type(), order='F')
 90        flag = 1
 91        checkCudaErrors(cuda.cuPointerSetAttribute(flag, cuda.CUpointer_attribute.CU_POINTER_ATTRIBUTE_SYNC_MEMOPS, self.buffer.data.ptr))
 92    
 93    def start_buffer_transfer(self, *args, buffer_type=SPCM_BUF_DATA, direction=None, notify_samples=None, transfer_offset=None, transfer_length=None, exception_num_samples=False) -> None:
 94        """
 95        Setup an RDMA transfer.
 96
 97        Parameters
 98        ----------
 99        *args : list
100            Additional commands that are send to the card.
101        direction : int
102            the direction of the transfer
103        notify_samples : int
104            Size of the part of the buffer that is used for notifications.
105        transfer_offset : int
106            the offset of the transfer
107        transfer_length : int
108            Total length of the transfer buffer.
109        """
110
111        # only change this locally
112        if notify_samples is not None:
113            notify_size = notify_samples * self.num_channels * self.bytes_per_sample
114        else:
115            notify_size = self.notify_size
116        transfer_offset = UnitConversion.convert(transfer_offset, units.Sa, int)
117        transfer_length = UnitConversion.convert(transfer_length, units.Sa, int)
118
119        if self.buffer is None: 
120            raise SpcmException(text="No buffer defined for transfer")
121        if buffer_type: 
122            self.buffer_type = buffer_type
123        if direction is None:
124            if self.direction == Direction.Acquisition:
125                direction = SPCM_DIR_CARDTOGPU
126            elif self.direction == Direction.Generation:
127                direction = SPCM_DIR_GPUTOCARD
128            else:
129                raise SpcmException(text="Please define a direction for transfer (SPCM_DIR_CARDTOGPU or SPCM_DIR_GPUTOCARD)")
130        
131        if self._notify_samples != 0 and np.remainder(self.buffer_samples, self._notify_samples) and exception_num_samples:
132            raise SpcmException("The number of samples needs to be a multiple of the notify samples.")
133
134        if transfer_offset:
135            transfer_offset_bytes = self.samples_to_bytes(transfer_offset)
136        else:
137            transfer_offset_bytes = 0
138
139        self.buffer_samples = transfer_length
140
141        # we define the buffer for transfer
142        self.card._print("Starting the DMA transfer and waiting until data is in board memory")
143        self.card._check_error(spcm_dwDefTransfer_i64(self.card._handle, self.buffer_type, direction, notify_size, c_void_p(self.buffer.data.ptr), transfer_offset_bytes, self.buffer_size))
144
145        # Execute additional commands if available
146        if args:
147            cmd = 0
148            for arg in args:
149                cmd |= arg
150            self.card.cmd(cmd)
151            self.card._print("... SCAPP data transfer started")

Class for data transfer between the card and the host using the SCAPP API.

SCAPPTransfer( card: Card, direction: spcm_core.pyspcm.Direction = <Direction.Acquisition: 1>)
67    def __init__(self, card : Card, direction : Direction = Direction.Acquisition):
68        if not _cuda_support:
69            raise ImportError("CUDA support is not available. Please install the cupy and cuda-python packages.")
70        super().__init__(card)
71        scapp_feature = bool(self.card._features & SPCM_FEAT_SCAPP)
72        if not scapp_feature:
73            raise SpcmException(text="The card does not have the SCAPP option installed. SCAPP is a add-on feature that needs to be bought separately, please contact info@spec.de and ask for the SCAPP option for the card with serial number {}".format(self.card.sn()))
74        self.direction = direction
75        self.iterator_index = 0

Initialize the DataTransfer object with a card object and additional arguments

Parameters
  • card (Card): the card object that is used for the data transfer
  • *args (list): list of additional arguments
  • **kwargs (dict): dictionary of additional keyword arguments
direction: spcm_core.pyspcm.Direction = None
iterator_index = 0
def allocate_buffer(self, num_samples: int) -> None:
77    def allocate_buffer(self, num_samples : int) -> None:
78        """
79        Memory allocation for the buffer that is used for communicating with the card
80
81        Parameters
82        ----------
83        num_samples : int | pint.Quantity = None
84            use the number of samples an get the number of active channels and bytes per samples directly from the card
85        """
86        
87        self.buffer_samples = UnitConversion.convert(num_samples, units.Sa, int)
88        # Allocate RDMA buffer
89        self.buffer = cp.empty((self.num_channels, self.buffer_samples), dtype = self.numpy_type(), order='F')
90        flag = 1
91        checkCudaErrors(cuda.cuPointerSetAttribute(flag, cuda.CUpointer_attribute.CU_POINTER_ATTRIBUTE_SYNC_MEMOPS, self.buffer.data.ptr))

Memory allocation for the buffer that is used for communicating with the card

Parameters
  • num_samples (int | pint.Quantity = None): use the number of samples an get the number of active channels and bytes per samples directly from the card
def start_buffer_transfer( self, *args, buffer_type=1000, direction=None, notify_samples=None, transfer_offset=None, transfer_length=None, exception_num_samples=False) -> None:
 93    def start_buffer_transfer(self, *args, buffer_type=SPCM_BUF_DATA, direction=None, notify_samples=None, transfer_offset=None, transfer_length=None, exception_num_samples=False) -> None:
 94        """
 95        Setup an RDMA transfer.
 96
 97        Parameters
 98        ----------
 99        *args : list
100            Additional commands that are send to the card.
101        direction : int
102            the direction of the transfer
103        notify_samples : int
104            Size of the part of the buffer that is used for notifications.
105        transfer_offset : int
106            the offset of the transfer
107        transfer_length : int
108            Total length of the transfer buffer.
109        """
110
111        # only change this locally
112        if notify_samples is not None:
113            notify_size = notify_samples * self.num_channels * self.bytes_per_sample
114        else:
115            notify_size = self.notify_size
116        transfer_offset = UnitConversion.convert(transfer_offset, units.Sa, int)
117        transfer_length = UnitConversion.convert(transfer_length, units.Sa, int)
118
119        if self.buffer is None: 
120            raise SpcmException(text="No buffer defined for transfer")
121        if buffer_type: 
122            self.buffer_type = buffer_type
123        if direction is None:
124            if self.direction == Direction.Acquisition:
125                direction = SPCM_DIR_CARDTOGPU
126            elif self.direction == Direction.Generation:
127                direction = SPCM_DIR_GPUTOCARD
128            else:
129                raise SpcmException(text="Please define a direction for transfer (SPCM_DIR_CARDTOGPU or SPCM_DIR_GPUTOCARD)")
130        
131        if self._notify_samples != 0 and np.remainder(self.buffer_samples, self._notify_samples) and exception_num_samples:
132            raise SpcmException("The number of samples needs to be a multiple of the notify samples.")
133
134        if transfer_offset:
135            transfer_offset_bytes = self.samples_to_bytes(transfer_offset)
136        else:
137            transfer_offset_bytes = 0
138
139        self.buffer_samples = transfer_length
140
141        # we define the buffer for transfer
142        self.card._print("Starting the DMA transfer and waiting until data is in board memory")
143        self.card._check_error(spcm_dwDefTransfer_i64(self.card._handle, self.buffer_type, direction, notify_size, c_void_p(self.buffer.data.ptr), transfer_offset_bytes, self.buffer_size))
144
145        # Execute additional commands if available
146        if args:
147            cmd = 0
148            for arg in args:
149                cmd |= arg
150            self.card.cmd(cmd)
151            self.card._print("... SCAPP data transfer started")

Setup an RDMA transfer.

Parameters
  • *args (list): Additional commands that are send to the card.
  • direction (int): the direction of the transfer
  • notify_samples (int): Size of the part of the buffer that is used for notifications.
  • transfer_offset (int): the offset of the transfer
  • transfer_length (int): Total length of the transfer buffer.
class SynchronousDigitalIOs(spcm.MultiPurposeIOs):
 17class SynchronousDigitalIOs(MultiPurposeIOs):
 18    """a higher-level abstraction of the CardFunctionality class to implement the Card's synchronuous digital I/O functionality"""
 19
 20    data_transfer : DataTransfer = None
 21    channels : Channels = None
 22    buffer : npt.NDArray = None
 23    item_size : int = None
 24    connections : dict = {}
 25    lowest_used_channel_bit : dict = {}
 26    setups : dict = {}
 27    channel_mask = {}
 28
 29    def __init__(self, data_transfer, channels, digin2bit : bool = False, *args, **kwargs) -> None:
 30        """
 31        Constructor for the SynchronousDigitalIOs class
 32    
 33        Parameters
 34        ----------
 35        data_transfer : DataTransfer
 36            The card object to communicate with the card
 37        channels : Channels
 38            The channels of the card
 39        digin2bit : bool = False (44xx only)
 40            If True, the digital input bits will be encoded as the 2 highest valued bits of the available channels
 41        """
 42
 43        self.data_transfer = data_transfer
 44        self.card = data_transfer.card
 45        self.channels = channels
 46        self.item_size = data_transfer.bytes_per_sample
 47        self.lowest_used_channel_bit = {}
 48        for channel in self.channels:
 49            self.lowest_used_channel_bit[int(channel)] = self.item_size * 8
 50            self.channel_mask[int(channel)] = 0
 51        self.connections = {}
 52        self.setups = {}
 53        self._max_buffer_index = -1
 54        self._buffer_iterator_index = -1
 55        self.num_xio_lines = self.get_num_xio_lines()
 56
 57        # For the 22xx and 23xx families there are only fixed xio lines possible
 58        if self.card.family() == 0x22 or self.card.family() == 0x23 or self.card.family() == 0x44:
 59            digin2bit &= (self.card.family() == 0x44)
 60            if digin2bit:
 61                self.x_mode(0, SPCM_XMODE_DIGIN2BIT)
 62            else:
 63                self.x_mode(0, SPCM_XMODE_DIGIN) # Turn-on one line turns on all the xio lines
 64            num_channels = len(channels)
 65            if digin2bit:
 66                num_channels *= 2
 67            num_buffers = np.min([num_channels, self.num_xio_lines])
 68            self._allocate_buffer(num_buffers)
 69            xios  = [0,1,2,0]
 70            xios2 = [1,2,0,1]
 71            if (self.card.family() == 0x22 or self.card.family() == 0x23) and (self.card.card_type() & TYP_CHMASK == 0x3 or self.card.card_type() & TYP_CHMASK == 0x1):
 72                if self.card.get_i(SPC_SAMPLERATE) > 1.25e9: # There is a bug in the 22xx family with one channel cards, see page 132 in the manual
 73                    print("Warning: this is the bug in the 22xx family with one channel cards, see page 132 in the manual")
 74                    self.lowest_used_channel_bit[1] -= 1
 75                    self.connections[0] = {'channel': 1, 'bit': self.lowest_used_channel_bit[1], 'xios': [0]}
 76                    self.lowest_used_channel_bit[0] -= 1
 77                    self.connections[1] = {'channel': 0, 'bit': self.lowest_used_channel_bit[0], 'xios': [2]}
 78                else:
 79                    self.lowest_used_channel_bit[0] -= 1
 80                    self.connections[0] = {'channel': 0, 'bit': self.lowest_used_channel_bit[0], 'xios': [0]}
 81                    self.lowest_used_channel_bit[1] -= 1
 82                    self.connections[1] = {'channel': 1, 'bit': self.lowest_used_channel_bit[1], 'xios': [2]}
 83            else:
 84                for channel in self.channels:
 85                    self.lowest_used_channel_bit[int(channel)] -= 1
 86                    self.connections[xios[int(channel)]] = {'channel': int(channel), 'bit': self.lowest_used_channel_bit[int(channel)], 'xios': [xios[int(channel)]]}
 87                    if digin2bit:
 88                        self.lowest_used_channel_bit[int(channel)] -= 1
 89                        self.connections[xios2[int(channel)]] = {'channel': int(channel), 'bit': self.lowest_used_channel_bit[int(channel)], 'xios': [xios2[int(channel)]]}
 90            pass
 91            
 92    def __str__(self) -> str:
 93        """
 94        String representation of the SynchronousDigitalIO class
 95    
 96        Returns
 97        -------
 98        str
 99            String representation of the SynchronousDigitalIO class
100        """
101        
102        return f"SynchronousDigitalIOs(data_transfer={self.data_transfer})"
103    
104    __repr__ = __str__
105
106    def __len__(self) -> int:
107        """
108        Get the number of buffers
109
110        Returns
111        -------
112        int
113            The number of buffers
114        """
115
116        return self._num_buffers
117
118    _buffer_iterator_index = -1
119    def __iter__(self) -> "SynchronousDigitalIOs":
120        """Define this class as an iterator"""
121        self._buffer_iterator_index = -1
122        return self
123        
124    def __getitem__(self, index : int):
125        """
126        Get the buffer at the specified index
127
128        Parameters
129        ----------
130        index : int
131            buffer index
132        """
133
134        return self.buffer[index, :]
135
136    def __next__(self) -> MultiPurposeIO:
137        """
138        This method is called when the next element is requested from the iterator
139
140        Returns
141        -------
142        MultiPurposeIO
143            The next xio line in the iterator
144        
145        Raises
146        ------
147        StopIteration
148        """
149        self._buffer_iterator_index += 1
150        if self._buffer_iterator_index >= self._num_buffers:
151            self._buffer_iterator_index = -1
152            raise StopIteration
153        return self.buffer[self._buffer_iterator_index, :]
154    
155    def current_xio(self) -> int:
156        """
157        Get the current xio line
158
159        Returns
160        -------
161        int
162            current xio line
163        """
164
165        return self.connections[self._buffer_iterator_index]['xios'][0]
166
167    def x_mode(self, xio : int, mode : int = None) -> int:
168        """
169        Sets the mode of the digital input/output of the card (see register 'SPCM_X0_MODE' in chapter `Multi Purpose I/O Lines` in the manual)
170    
171        Parameters
172        ----------
173        mode : int
174            The mode of the digital input/output
175        
176        Returns
177        -------
178        int
179            The mode of the digital input/output
180        """
181
182        if mode is not None:
183            self.card.set_i(SPCM_X0_MODE + xio, mode)
184        return self.card.get_i(SPCM_X0_MODE + xio)
185
186    def digmode(self, channel : int, mode : int = None) -> int:
187        """
188        Sets the mode of the digital input of the card (see register 'SPC_DIGMODE0' in chapter `Synchronous digital inputs` in the manual)
189    
190        Parameters
191        ----------
192        mode : int
193            The mode of the digital input
194        
195        Returns
196        -------
197        int
198            The mode of the digital input
199        """
200
201        if mode is not None:
202            self.card.set_i(SPC_DIGMODE0 + channel, mode)
203        return self.card.get_i(SPC_DIGMODE0 + channel)
204
205    def allocate_buffer(self, num_buffers : int) -> npt.NDArray:
206        """
207        Allocate the buffers for the digital input/output lines of the card
208
209        Parameters
210        ----------
211        num_buffers : int
212            The number of buffers to allocate
213        """
214
215        if self.card.family() == 0x22 or self.card.family() == 0x23 or self.card.family() == 0x44:
216            print("The 22xx, 23xx and 44xx families only support fixed xio lines, allocate_buffer() is not necessary and doesn't change the system")
217        else:
218            self._allocate_buffer(num_buffers)
219        return self.buffer
220    
221    def _allocate_buffer(self, num_buffers : int) -> npt.NDArray:
222        """
223        Allocate the buffers for the digital input/output lines of the card
224
225        Parameters
226        ----------
227        num_buffers : int
228            The number of buffers to allocate
229        """
230
231        self._num_buffers = num_buffers
232        # TODO make this usable with multiple replay/recording
233        self.buffer = np.zeros((self._num_buffers, self.data_transfer.buffer.shape[-1]), dtype=self.data_transfer.numpy_type())
234        return self.buffer
235
236    def setup(self, buffer_index : int, channel, xios : list) -> None:
237        """
238        Register the connection of a buffer with the corresponding bit in a specific channel of the card
239
240        Parameters
241        ----------
242        buffer_index : int
243            The index of the buffer to be used
244        channel : Channel | int
245            The channel index
246        xios : list | int
247            The xio lines that the buffer is connected to
248        """
249
250        if self.card.family() == 0x22 or self.card.family() == 0x23 or self.card.family() == 0x44:
251            print("The 22xx, 23xx and 44x family only support fixed xio lines, setup() is not necessary and doesn't change the system")
252            return
253
254        # Define the buffer
255        if isinstance(xios, int):
256            xios = [xios]
257        self.lowest_used_channel_bit[int(channel)] -= 1
258        self.connections[buffer_index] = {'channel': int(channel), 'bit': self.lowest_used_channel_bit[int(channel)], 'xios': xios}
259
260        # Setup the right xio mode
261        if self.data_transfer.direction == Direction.Generation:
262            bit_mask = SPCM_XMODE_DIGOUTSRC_BIT15 << (15 - self.lowest_used_channel_bit[int(channel)])
263            channel_mask = SPCM_XMODE_DIGOUTSRC_CH0 << int(channel)
264            for xio in xios:
265                self.x_mode(xio, SPCM_XMODE_DIGOUT | channel_mask | bit_mask)
266        elif self.data_transfer.direction == Direction.Acquisition:
267            bit_mask = DIGMODEMASK_BIT15 >> 5*(15 - self.lowest_used_channel_bit[int(channel)])
268            x_mask = SPCM_DIGMODE_X0 + (xios[0]) * (SPCM_DIGMODE_X1 - SPCM_DIGMODE_X0)
269            self.channel_mask[int(channel)] |= bit_mask & x_mask
270            self.x_mode(xios[0], SPCM_XMODE_DIGIN)
271            self.digmode(int(channel), self.channel_mask[int(channel)])
272        else:
273            raise SpcmException(text="Please specify a data transfer direction: (Acquisition or Generation)")
274    
275    def process(self, no_shift : bool = False):
276        """
277        Process the digital input/output lines of the card
278
279        Parameters
280        ----------
281        no_shift : bool
282            If True, no bit shift will be applied
283        """
284
285        itemsize_bits = self.item_size * 8
286        uint_type = self._int2uint(self.data_transfer.numpy_type())
287        if self.data_transfer.direction == Direction.Generation:
288            if not no_shift:
289                for channel, lowest_bit in self.lowest_used_channel_bit.items():
290                    bit_shift = itemsize_bits - lowest_bit
291                    self.data_transfer.buffer[channel, :] = (self.data_transfer.buffer[channel, :].view(uint_type) >> bit_shift)
292
293            for key, connection in self.connections.items():
294                self.data_transfer.buffer[connection['channel'], :] |= self.buffer[key, :] << connection['bit']
295        elif self.data_transfer.direction == Direction.Acquisition:
296            for buffer_index in range(self._num_buffers):
297                channel_index = int(self.connections[buffer_index]['channel'])
298                self.buffer[buffer_index, :] = (self.data_transfer.buffer[channel_index, :] >> self.connections[buffer_index]['bit']) & 0x1
299
300            if not no_shift:
301                for channel, lowest_bit in self.lowest_used_channel_bit.items():
302                    bit_shift = itemsize_bits - lowest_bit
303                    self.data_transfer.buffer[channel, :] = (self.data_transfer.buffer[channel, :].view(uint_type) << bit_shift)
304        else:
305            raise SpcmException(text="Please specify a data transfer direction: (Acquisition or Generation)")
306        
307    def _int2uint(self, np_type : npt.DTypeLike) -> npt.DTypeLike:
308        """
309        Convert the integer data to unsigned integer data
310        """
311
312        if np_type == np.int8:
313            return np.uint8
314        elif np_type == np.int16:
315            return np.uint16
316        elif np_type == np.int32:
317            return np.uint32
318        elif np_type == np.int64:
319            return np.uint64
320        else:
321            raise SpcmException(text="The data type is not supported for signed to unsigned conversion")

a higher-level abstraction of the CardFunctionality class to implement the Card's synchronuous digital I/O functionality

SynchronousDigitalIOs(data_transfer, channels, digin2bit: bool = False, *args, **kwargs)
29    def __init__(self, data_transfer, channels, digin2bit : bool = False, *args, **kwargs) -> None:
30        """
31        Constructor for the SynchronousDigitalIOs class
32    
33        Parameters
34        ----------
35        data_transfer : DataTransfer
36            The card object to communicate with the card
37        channels : Channels
38            The channels of the card
39        digin2bit : bool = False (44xx only)
40            If True, the digital input bits will be encoded as the 2 highest valued bits of the available channels
41        """
42
43        self.data_transfer = data_transfer
44        self.card = data_transfer.card
45        self.channels = channels
46        self.item_size = data_transfer.bytes_per_sample
47        self.lowest_used_channel_bit = {}
48        for channel in self.channels:
49            self.lowest_used_channel_bit[int(channel)] = self.item_size * 8
50            self.channel_mask[int(channel)] = 0
51        self.connections = {}
52        self.setups = {}
53        self._max_buffer_index = -1
54        self._buffer_iterator_index = -1
55        self.num_xio_lines = self.get_num_xio_lines()
56
57        # For the 22xx and 23xx families there are only fixed xio lines possible
58        if self.card.family() == 0x22 or self.card.family() == 0x23 or self.card.family() == 0x44:
59            digin2bit &= (self.card.family() == 0x44)
60            if digin2bit:
61                self.x_mode(0, SPCM_XMODE_DIGIN2BIT)
62            else:
63                self.x_mode(0, SPCM_XMODE_DIGIN) # Turn-on one line turns on all the xio lines
64            num_channels = len(channels)
65            if digin2bit:
66                num_channels *= 2
67            num_buffers = np.min([num_channels, self.num_xio_lines])
68            self._allocate_buffer(num_buffers)
69            xios  = [0,1,2,0]
70            xios2 = [1,2,0,1]
71            if (self.card.family() == 0x22 or self.card.family() == 0x23) and (self.card.card_type() & TYP_CHMASK == 0x3 or self.card.card_type() & TYP_CHMASK == 0x1):
72                if self.card.get_i(SPC_SAMPLERATE) > 1.25e9: # There is a bug in the 22xx family with one channel cards, see page 132 in the manual
73                    print("Warning: this is the bug in the 22xx family with one channel cards, see page 132 in the manual")
74                    self.lowest_used_channel_bit[1] -= 1
75                    self.connections[0] = {'channel': 1, 'bit': self.lowest_used_channel_bit[1], 'xios': [0]}
76                    self.lowest_used_channel_bit[0] -= 1
77                    self.connections[1] = {'channel': 0, 'bit': self.lowest_used_channel_bit[0], 'xios': [2]}
78                else:
79                    self.lowest_used_channel_bit[0] -= 1
80                    self.connections[0] = {'channel': 0, 'bit': self.lowest_used_channel_bit[0], 'xios': [0]}
81                    self.lowest_used_channel_bit[1] -= 1
82                    self.connections[1] = {'channel': 1, 'bit': self.lowest_used_channel_bit[1], 'xios': [2]}
83            else:
84                for channel in self.channels:
85                    self.lowest_used_channel_bit[int(channel)] -= 1
86                    self.connections[xios[int(channel)]] = {'channel': int(channel), 'bit': self.lowest_used_channel_bit[int(channel)], 'xios': [xios[int(channel)]]}
87                    if digin2bit:
88                        self.lowest_used_channel_bit[int(channel)] -= 1
89                        self.connections[xios2[int(channel)]] = {'channel': int(channel), 'bit': self.lowest_used_channel_bit[int(channel)], 'xios': [xios2[int(channel)]]}
90            pass

Constructor for the SynchronousDigitalIOs class

Parameters
  • data_transfer (DataTransfer): The card object to communicate with the card
  • channels (Channels): The channels of the card
  • digin2bit (bool = False (44xx only)): If True, the digital input bits will be encoded as the 2 highest valued bits of the available channels
data_transfer: DataTransfer = None
channels: Channels = None
buffer: numpy.ndarray[tuple[int, ...], numpy.dtype[+_ScalarType_co]] = None
item_size: int = None
connections: dict = {}
lowest_used_channel_bit: dict = {}
setups: dict = {}
channel_mask = {}
card
num_xio_lines = None
def current_xio(self) -> int:
155    def current_xio(self) -> int:
156        """
157        Get the current xio line
158
159        Returns
160        -------
161        int
162            current xio line
163        """
164
165        return self.connections[self._buffer_iterator_index]['xios'][0]

Get the current xio line

Returns
  • int: current xio line
def x_mode(self, xio: int, mode: int = None) -> int:
167    def x_mode(self, xio : int, mode : int = None) -> int:
168        """
169        Sets the mode of the digital input/output of the card (see register 'SPCM_X0_MODE' in chapter `Multi Purpose I/O Lines` in the manual)
170    
171        Parameters
172        ----------
173        mode : int
174            The mode of the digital input/output
175        
176        Returns
177        -------
178        int
179            The mode of the digital input/output
180        """
181
182        if mode is not None:
183            self.card.set_i(SPCM_X0_MODE + xio, mode)
184        return self.card.get_i(SPCM_X0_MODE + xio)

Sets the mode of the digital input/output of the card (see register 'SPCM_X0_MODE' in chapter Multi Purpose I/O Lines in the manual)

Parameters
  • mode (int): The mode of the digital input/output
Returns
  • int: The mode of the digital input/output
def digmode(self, channel: int, mode: int = None) -> int:
186    def digmode(self, channel : int, mode : int = None) -> int:
187        """
188        Sets the mode of the digital input of the card (see register 'SPC_DIGMODE0' in chapter `Synchronous digital inputs` in the manual)
189    
190        Parameters
191        ----------
192        mode : int
193            The mode of the digital input
194        
195        Returns
196        -------
197        int
198            The mode of the digital input
199        """
200
201        if mode is not None:
202            self.card.set_i(SPC_DIGMODE0 + channel, mode)
203        return self.card.get_i(SPC_DIGMODE0 + channel)

Sets the mode of the digital input of the card (see register 'SPC_DIGMODE0' in chapter Synchronous digital inputs in the manual)

Parameters
  • mode (int): The mode of the digital input
Returns
  • int: The mode of the digital input
def allocate_buffer( self, num_buffers: int) -> numpy.ndarray[tuple[int, ...], numpy.dtype[+_ScalarType_co]]:
205    def allocate_buffer(self, num_buffers : int) -> npt.NDArray:
206        """
207        Allocate the buffers for the digital input/output lines of the card
208
209        Parameters
210        ----------
211        num_buffers : int
212            The number of buffers to allocate
213        """
214
215        if self.card.family() == 0x22 or self.card.family() == 0x23 or self.card.family() == 0x44:
216            print("The 22xx, 23xx and 44xx families only support fixed xio lines, allocate_buffer() is not necessary and doesn't change the system")
217        else:
218            self._allocate_buffer(num_buffers)
219        return self.buffer

Allocate the buffers for the digital input/output lines of the card

Parameters
  • num_buffers (int): The number of buffers to allocate
def setup(self, buffer_index: int, channel, xios: list) -> None:
236    def setup(self, buffer_index : int, channel, xios : list) -> None:
237        """
238        Register the connection of a buffer with the corresponding bit in a specific channel of the card
239
240        Parameters
241        ----------
242        buffer_index : int
243            The index of the buffer to be used
244        channel : Channel | int
245            The channel index
246        xios : list | int
247            The xio lines that the buffer is connected to
248        """
249
250        if self.card.family() == 0x22 or self.card.family() == 0x23 or self.card.family() == 0x44:
251            print("The 22xx, 23xx and 44x family only support fixed xio lines, setup() is not necessary and doesn't change the system")
252            return
253
254        # Define the buffer
255        if isinstance(xios, int):
256            xios = [xios]
257        self.lowest_used_channel_bit[int(channel)] -= 1
258        self.connections[buffer_index] = {'channel': int(channel), 'bit': self.lowest_used_channel_bit[int(channel)], 'xios': xios}
259
260        # Setup the right xio mode
261        if self.data_transfer.direction == Direction.Generation:
262            bit_mask = SPCM_XMODE_DIGOUTSRC_BIT15 << (15 - self.lowest_used_channel_bit[int(channel)])
263            channel_mask = SPCM_XMODE_DIGOUTSRC_CH0 << int(channel)
264            for xio in xios:
265                self.x_mode(xio, SPCM_XMODE_DIGOUT | channel_mask | bit_mask)
266        elif self.data_transfer.direction == Direction.Acquisition:
267            bit_mask = DIGMODEMASK_BIT15 >> 5*(15 - self.lowest_used_channel_bit[int(channel)])
268            x_mask = SPCM_DIGMODE_X0 + (xios[0]) * (SPCM_DIGMODE_X1 - SPCM_DIGMODE_X0)
269            self.channel_mask[int(channel)] |= bit_mask & x_mask
270            self.x_mode(xios[0], SPCM_XMODE_DIGIN)
271            self.digmode(int(channel), self.channel_mask[int(channel)])
272        else:
273            raise SpcmException(text="Please specify a data transfer direction: (Acquisition or Generation)")

Register the connection of a buffer with the corresponding bit in a specific channel of the card

Parameters
  • buffer_index (int): The index of the buffer to be used
  • channel (Channel | int): The channel index
  • xios (list | int): The xio lines that the buffer is connected to
def process(self, no_shift: bool = False):
275    def process(self, no_shift : bool = False):
276        """
277        Process the digital input/output lines of the card
278
279        Parameters
280        ----------
281        no_shift : bool
282            If True, no bit shift will be applied
283        """
284
285        itemsize_bits = self.item_size * 8
286        uint_type = self._int2uint(self.data_transfer.numpy_type())
287        if self.data_transfer.direction == Direction.Generation:
288            if not no_shift:
289                for channel, lowest_bit in self.lowest_used_channel_bit.items():
290                    bit_shift = itemsize_bits - lowest_bit
291                    self.data_transfer.buffer[channel, :] = (self.data_transfer.buffer[channel, :].view(uint_type) >> bit_shift)
292
293            for key, connection in self.connections.items():
294                self.data_transfer.buffer[connection['channel'], :] |= self.buffer[key, :] << connection['bit']
295        elif self.data_transfer.direction == Direction.Acquisition:
296            for buffer_index in range(self._num_buffers):
297                channel_index = int(self.connections[buffer_index]['channel'])
298                self.buffer[buffer_index, :] = (self.data_transfer.buffer[channel_index, :] >> self.connections[buffer_index]['bit']) & 0x1
299
300            if not no_shift:
301                for channel, lowest_bit in self.lowest_used_channel_bit.items():
302                    bit_shift = itemsize_bits - lowest_bit
303                    self.data_transfer.buffer[channel, :] = (self.data_transfer.buffer[channel, :].view(uint_type) << bit_shift)
304        else:
305            raise SpcmException(text="Please specify a data transfer direction: (Acquisition or Generation)")

Process the digital input/output lines of the card

Parameters
  • no_shift (bool): If True, no bit shift will be applied