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-corefor 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:
- Digitizers
- M2p
- M4i / M4x
- M5i
- digitizerNetbox
- Arbitrary Waveform Generators (AWGs) and DDS Generators
- M2p
- M4i / M4x
- M5i
- generatorNetbox
- Digital Waveform Acquisition/Pattern Generation
- M2p
- M4i / M4x
- Hybrid
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, SCAPPMulti 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", "SCAPPMulti", "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)
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 def timeout(self, timeout : int = None, return_unit = None) -> int: 305 """ 306 Sets the timeout in ms (see register `SPC_TIMEOUT` in the manual) 307 308 Parameters 309 ---------- 310 timeout : int 311 The timeout in ms 312 313 Returns 314 ------- 315 int 316 returns the current timeout in ms 317 """ 318 319 if timeout is not None: 320 timeout = UnitConversion.convert(timeout, units.ms, int) 321 self.set_i(SPC_TIMEOUT, timeout) 322 return_value = self.get_i(SPC_TIMEOUT) 323 return_value = UnitConversion.to_unit(return_value * units.ms, return_unit) 324 return return_value 325 326 def start(self, *args) -> None: 327 """ 328 Starts the connected card and enables triggering on the card (see command `M2CMD_CARD_START` in the manual) 329 330 Parameters 331 ---------- 332 *args : int 333 flags that are send together with the start command 334 """ 335 336 self.cmd(M2CMD_CARD_START, *args) 337 338 def stop(self, *args : int) -> None: 339 """ 340 Stops the connected card (see command `M2CMD_CARD_STOP` in the manual) 341 342 Parameters 343 ---------- 344 *args : int 345 flags that are send together with the stop command (e.g. M2CMD_DATA_STOPDMA) 346 """ 347 348 self.cmd(M2CMD_CARD_STOP, *args) 349 350 def reset(self) -> None: 351 """ 352 Resets the connected device (see command `M2CMD_CARD_RESET` in the manual) 353 """ 354 355 self.cmd(M2CMD_CARD_RESET) 356 357 def write_setup(self, *args) -> None: 358 """ 359 Writes of the configuration registers previously changed to the device (see command `M2CMD_CARD_WRITESETUP` in the manual) 360 361 Parameters 362 ---------- 363 *args : int 364 flags that are set with the write command 365 """ 366 367 self.cmd(M2CMD_CARD_WRITESETUP, *args) 368 369 def register_list(self, register_list : List[dict[str, Union[int, float]]]) -> None: 370 """ 371 Writes a list with dictionaries, where each dictionary corresponds to a command (see the user manual of your device for all the available registers) 372 373 Parameters 374 ---------- 375 register_list : List[dict[str, Union[int, float]]] 376 The list of commands that needs to written to the specific registers of the card. 377 """ 378 379 c_astParams = (ST_LIST_PARAM * 1024)() 380 astParams = ctypes.cast(c_astParams, ctypes.POINTER(ST_LIST_PARAM)) 381 for i, register in enumerate(register_list): 382 astParams[i].lReg = register["lReg"] 383 astParams[i].lType = register["lType"] 384 if register["lType"] == TYPE_INT64: 385 astParams[i].Value.llValue = register["llValue"] 386 elif register["lType"] == TYPE_DOUBLE: 387 astParams[i].Value.dValue = register["dValue"] 388 self.set_ptr(SPC_REGISTER_LIST, astParams, len(register_list) * ctypes.sizeof(ST_LIST_PARAM)) 389 390 # Low-level get and set functions 391 def get_i(self, register : int) -> int: 392 """ 393 Get the integer value of a specific register of the card (see the user manual of your device for all the available registers) 394 395 Parameters 396 ---------- 397 register : int 398 The specific register that will be read from. 399 400 Returns 401 ------- 402 int 403 The value as stored in the specific register 404 """ 405 406 self._check_closed() 407 return_value = int64(0) 408 self._check_error(spcm_dwGetParam_i64(self._handle, register, byref(return_value))) 409 return return_value.value 410 get = get_i 411 """Alias of get_i""" 412 413 def get_d(self, register : int) -> float: 414 """ 415 Get the float value of a specific register of the card (see the user manual of your device for all the available registers) 416 417 Parameters 418 ---------- 419 register : int 420 The specific register that will be read from. 421 422 Returns 423 ------- 424 float 425 The value as stored in the specific register 426 """ 427 428 self._check_closed() 429 return_value = c_double(0) 430 self._check_error(spcm_dwGetParam_d64(self._handle, register, byref(return_value))) 431 return return_value.value 432 433 def get_str(self, register : int) -> str: 434 """ 435 Get the string value of a specific register of the card (see the user manual of your device for all the available registers) 436 437 Parameters 438 ---------- 439 register : int 440 The specific register that will be read from. 441 442 Returns 443 ------- 444 str 445 The value as stored in the specific register 446 """ 447 448 self._check_closed() 449 return_value = create_string_buffer(self._str_len) 450 self._check_error(spcm_dwGetParam_ptr(self._handle, register, byref(return_value), self._str_len)) 451 return return_value.value.decode('utf-8') 452 453 def set_i(self, register : int, value : int) -> None: 454 """ 455 Write the value of a specific register to the card (see the user manual of your device for all the available registers) 456 457 Parameters 458 ---------- 459 register : int 460 The specific register that will be written. 461 value : int 462 The value that is written to the card. 463 """ 464 465 self._check_closed() 466 self._check_error(spcm_dwSetParam_i64(self._handle, register, value)) 467 468 def set_d(self, register : int, value : float) -> None: 469 """ 470 Write the value of a specific register to the card (see the user manual of your device for all the available registers) 471 472 Parameters 473 ---------- 474 register : int 475 The specific register that will be written. 476 value : float 477 The value that is written to the card. 478 """ 479 480 self._check_closed() 481 self._check_error(spcm_dwSetParam_d64(self._handle, register, value)) 482 483 def set_ptr(self, register : int, reference : c_void_p, size : int) -> None: 484 """ 485 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) 486 487 Parameters 488 ---------- 489 register : int 490 The specific register that will be read from. 491 reference : c_void_p 492 pointer to the memory segment 493 size : int 494 size of the memory segment 495 496 Returns 497 ------- 498 int 499 The value as stored in the specific register 500 """ 501 502 self._check_closed() 503 self._check_error(spcm_dwSetParam_ptr(self._handle, register, reference, size)) 504 505 # Error handling and exception raising 506 def _check_error(self, dwErr : int, call_stack : int = 4): 507 """ 508 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) 509 510 Parameters 511 ---------- 512 dwErr : int 513 The error value as returned from a direct driver call 514 call_stack : int 515 The number of stack frames to skip when creating the traceback for the exception 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
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
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
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
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
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
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
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
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
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
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.
304 def timeout(self, timeout : int = None, return_unit = None) -> int: 305 """ 306 Sets the timeout in ms (see register `SPC_TIMEOUT` in the manual) 307 308 Parameters 309 ---------- 310 timeout : int 311 The timeout in ms 312 313 Returns 314 ------- 315 int 316 returns the current timeout in ms 317 """ 318 319 if timeout is not None: 320 timeout = UnitConversion.convert(timeout, units.ms, int) 321 self.set_i(SPC_TIMEOUT, timeout) 322 return_value = self.get_i(SPC_TIMEOUT) 323 return_value = UnitConversion.to_unit(return_value * units.ms, return_unit) 324 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
326 def start(self, *args) -> None: 327 """ 328 Starts the connected card and enables triggering on the card (see command `M2CMD_CARD_START` in the manual) 329 330 Parameters 331 ---------- 332 *args : int 333 flags that are send together with the start command 334 """ 335 336 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
338 def stop(self, *args : int) -> None: 339 """ 340 Stops the connected card (see command `M2CMD_CARD_STOP` in the manual) 341 342 Parameters 343 ---------- 344 *args : int 345 flags that are send together with the stop command (e.g. M2CMD_DATA_STOPDMA) 346 """ 347 348 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)
350 def reset(self) -> None: 351 """ 352 Resets the connected device (see command `M2CMD_CARD_RESET` in the manual) 353 """ 354 355 self.cmd(M2CMD_CARD_RESET)
Resets the connected device (see command M2CMD_CARD_RESET in the manual)
357 def write_setup(self, *args) -> None: 358 """ 359 Writes of the configuration registers previously changed to the device (see command `M2CMD_CARD_WRITESETUP` in the manual) 360 361 Parameters 362 ---------- 363 *args : int 364 flags that are set with the write command 365 """ 366 367 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
369 def register_list(self, register_list : List[dict[str, Union[int, float]]]) -> None: 370 """ 371 Writes a list with dictionaries, where each dictionary corresponds to a command (see the user manual of your device for all the available registers) 372 373 Parameters 374 ---------- 375 register_list : List[dict[str, Union[int, float]]] 376 The list of commands that needs to written to the specific registers of the card. 377 """ 378 379 c_astParams = (ST_LIST_PARAM * 1024)() 380 astParams = ctypes.cast(c_astParams, ctypes.POINTER(ST_LIST_PARAM)) 381 for i, register in enumerate(register_list): 382 astParams[i].lReg = register["lReg"] 383 astParams[i].lType = register["lType"] 384 if register["lType"] == TYPE_INT64: 385 astParams[i].Value.llValue = register["llValue"] 386 elif register["lType"] == TYPE_DOUBLE: 387 astParams[i].Value.dValue = register["dValue"] 388 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.
391 def get_i(self, register : int) -> int: 392 """ 393 Get the integer value of a specific register of the card (see the user manual of your device for all the available registers) 394 395 Parameters 396 ---------- 397 register : int 398 The specific register that will be read from. 399 400 Returns 401 ------- 402 int 403 The value as stored in the specific register 404 """ 405 406 self._check_closed() 407 return_value = int64(0) 408 self._check_error(spcm_dwGetParam_i64(self._handle, register, byref(return_value))) 409 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
391 def get_i(self, register : int) -> int: 392 """ 393 Get the integer value of a specific register of the card (see the user manual of your device for all the available registers) 394 395 Parameters 396 ---------- 397 register : int 398 The specific register that will be read from. 399 400 Returns 401 ------- 402 int 403 The value as stored in the specific register 404 """ 405 406 self._check_closed() 407 return_value = int64(0) 408 self._check_error(spcm_dwGetParam_i64(self._handle, register, byref(return_value))) 409 return return_value.value
Alias of get_i
413 def get_d(self, register : int) -> float: 414 """ 415 Get the float value of a specific register of the card (see the user manual of your device for all the available registers) 416 417 Parameters 418 ---------- 419 register : int 420 The specific register that will be read from. 421 422 Returns 423 ------- 424 float 425 The value as stored in the specific register 426 """ 427 428 self._check_closed() 429 return_value = c_double(0) 430 self._check_error(spcm_dwGetParam_d64(self._handle, register, byref(return_value))) 431 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
433 def get_str(self, register : int) -> str: 434 """ 435 Get the string value of a specific register of the card (see the user manual of your device for all the available registers) 436 437 Parameters 438 ---------- 439 register : int 440 The specific register that will be read from. 441 442 Returns 443 ------- 444 str 445 The value as stored in the specific register 446 """ 447 448 self._check_closed() 449 return_value = create_string_buffer(self._str_len) 450 self._check_error(spcm_dwGetParam_ptr(self._handle, register, byref(return_value), self._str_len)) 451 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
453 def set_i(self, register : int, value : int) -> None: 454 """ 455 Write the value of a specific register to the card (see the user manual of your device for all the available registers) 456 457 Parameters 458 ---------- 459 register : int 460 The specific register that will be written. 461 value : int 462 The value that is written to the card. 463 """ 464 465 self._check_closed() 466 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.
468 def set_d(self, register : int, value : float) -> None: 469 """ 470 Write the value of a specific register to the card (see the user manual of your device for all the available registers) 471 472 Parameters 473 ---------- 474 register : int 475 The specific register that will be written. 476 value : float 477 The value that is written to the card. 478 """ 479 480 self._check_closed() 481 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.
483 def set_ptr(self, register : int, reference : c_void_p, size : int) -> None: 484 """ 485 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) 486 487 Parameters 488 ---------- 489 register : int 490 The specific register that will be read from. 491 reference : c_void_p 492 pointer to the memory segment 493 size : int 494 size of the memory segment 495 496 Returns 497 ------- 498 int 499 The value as stored in the specific register 500 """ 501 502 self._check_closed() 503 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
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
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)
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.
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
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
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
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
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
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
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.
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
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
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
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
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
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
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
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
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)
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)
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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)
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)
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
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
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
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
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
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
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.
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
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
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
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
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
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
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)
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
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
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
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:
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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")
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
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
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?):
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
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
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
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
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
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 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
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
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
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
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
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
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 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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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)
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)
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)
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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)
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
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).
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
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
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
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
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
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
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
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
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
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
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.
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
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
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
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 transfer_offset : int = 0 55 buffer_size : int = 0 56 notify_size : int = 0 57 58 direction : Direction = Direction.Acquisition 59 _direction : int = 0 60 61 buffer_type : int 62 num_channels : int = 0 63 bytes_per_sample : int = 0 64 bits_per_sample : int = 0 65 66 current_user_pos : int = 0 67 68 _polling = False 69 _polling_timer = 0 70 71 # private 72 _buffer_samples : int = 0 73 _notify_samples : int = 0 74 75 # private 76 _memory_size : int = 0 77 _c_buffer = None # Internal numpy ctypes buffer object 78 _buffer_alignment : int = 4096 79 _np_buffer : npt.NDArray[np.int_] # Internal object on which the getter setter logic is working 80 _bit_buffer : npt.NDArray[np.int_] 81 _8bit_mode : bool = False 82 _12bit_mode : bool = False 83 _unpack : bool = False # if True, the buffer is automatically unpacked if the mode is 12bit packed (WARNING: this might slow down the processing) 84 _pre_trigger : int = 0 85 86 def __init__(self, card, *args, **kwargs) -> None: 87 """ 88 Initialize the DataTransfer object with a card object and additional arguments 89 90 Parameters 91 ---------- 92 card : Card 93 the card object that is used for the data transfer 94 *args : list 95 list of additional arguments 96 **kwargs : dict 97 dictionary of additional keyword arguments 98 """ 99 100 self.transfer_offset = 0 101 self.buffer_size = 0 102 self.notify_size = 0 103 self.num_channels = 0 104 self.bytes_per_sample = 0 105 self.bits_per_sample = 0 106 107 self.current_user_pos = 0 108 109 self._buffer_samples = 0 110 self._notify_samples = 0 111 self._memory_size = 0 112 self._c_buffer = None 113 self._buffer_alignment = 4096 114 self._np_buffer = None 115 self._bit_buffer = None 116 self._8bit_mode = False 117 self._12bit_mode = False 118 self._pre_trigger = 0 119 120 super().__init__(card, *args, **kwargs) 121 self.buffer_type = SPCM_BUF_DATA 122 self._bytes_per_sample() 123 self._bits_per_sample() 124 self.num_channels = self.card.active_channels() 125 126 # Find out the direction of transfer 127 if self.function_type == SPCM_TYPE_AI or self.function_type == SPCM_TYPE_DI: 128 self.direction = Direction.Acquisition 129 self._direction = SPCM_DIR_CARDTOPC 130 elif self.function_type == SPCM_TYPE_AO or self.function_type == SPCM_TYPE_DO: 131 self.direction = Direction.Generation 132 self._direction = SPCM_DIR_PCTOCARD 133 else: 134 self.direction = Direction.Undefined 135 self._direction = 0 136 137 138 @property 139 def buffer(self) -> npt.NDArray[np.int_]: 140 """ 141 The numpy buffer object that interfaces the Card and can be written and read from 142 143 Returns 144 ------- 145 numpy array 146 the numpy buffer object with the following array index definition: 147 `[channel, sample]` 148 or in case of multiple recording / replay: 149 `[segment, sample, channel]` 150 """ 151 return self._np_buffer 152 153 @buffer.setter 154 def buffer(self, value) -> None: 155 self._np_buffer = value 156 157 @buffer.deleter 158 def buffer(self) -> None: 159 del self._np_buffer 160 161 @property 162 def bit_buffer(self) -> npt.NDArray[np.int_]: 163 """ 164 The bit buffer object that interfaces the Card and can be written and read from 165 166 Returns 167 ------- 168 numpy array 169 with the buffer object where all the individual bits are now unpacked 170 """ 171 172 return self._bit_buffer 173 174 @bit_buffer.setter 175 def bit_buffer(self, value) -> None: 176 self._bit_buffer = value 177 178 @bit_buffer.deleter 179 def bit_buffer(self) -> None: 180 del self._bit_buffer 181 182 @property 183 def buffer_samples(self) -> int: 184 """ 185 The number of samples in the buffer 186 187 Returns 188 ------- 189 int 190 the number of samples in the buffer 191 """ 192 return self._buffer_samples 193 194 @buffer_samples.setter 195 def buffer_samples(self, value) -> None: 196 if value is not None: 197 self._buffer_samples = value 198 self.buffer_size = self.samples_to_bytes(self._buffer_samples) 199 200 @buffer_samples.deleter 201 def buffer_samples(self) -> None: 202 del self._buffer_samples 203 204 def _bits_per_sample(self) -> int: 205 """ 206 Get the number of bits per sample 207 208 Returns 209 ------- 210 int 211 number of bits per sample 212 """ 213 if self._8bit_mode: 214 self.bits_per_sample = 8 215 elif self._12bit_mode: 216 self.bits_per_sample = 12 217 else: 218 self.bits_per_sample = self.card.bits_per_sample() 219 return self.bits_per_sample 220 221 def _bytes_per_sample(self) -> int: 222 """ 223 Get the number of bytes per sample 224 225 Returns 226 ------- 227 int 228 number of bytes per sample 229 """ 230 if self._8bit_mode: 231 self.bytes_per_sample = 1 232 elif self._12bit_mode: 233 self.bytes_per_sample = 1.5 234 else: 235 self.bytes_per_sample = self.card.bytes_per_sample() 236 return self.bytes_per_sample 237 238 def bytes_to_samples(self, num_bytes : int) -> int: 239 """ 240 Convert bytes to samples 241 242 Parameters 243 ---------- 244 bytes : int 245 the number of bytes 246 247 Returns 248 ------- 249 int 250 the number of samples 251 """ 252 253 if self.bits_per_sample > 1: 254 num_samples = int(num_bytes // self.bytes_per_sample // self.num_channels) 255 else: 256 num_samples = int(num_bytes // self.num_channels * 8) 257 return num_samples 258 259 def samples_to_bytes(self, num_samples : int) -> int: 260 """ 261 Convert samples to bytes 262 263 Parameters 264 ---------- 265 num_samples : int 266 the number of samples 267 268 Returns 269 ------- 270 int 271 the number of bytes 272 """ 273 274 if self.bits_per_sample > 1: 275 num_bytes = int(num_samples * self.bytes_per_sample * self.num_channels) 276 else: 277 num_bytes = int(num_samples * self.num_channels // 8) 278 return num_bytes 279 280 def notify_samples(self, notify_samples : int = None) -> int: 281 """ 282 Set the number of samples to notify the user about 283 284 Parameters 285 ---------- 286 notify_samples : int | pint.Quantity 287 the number of samples to notify the user about 288 """ 289 290 if notify_samples is not None: 291 notify_samples = UnitConversion.convert(notify_samples, units.Sa, int) 292 self._notify_samples = notify_samples 293 self.notify_size = self.samples_to_bytes(self._notify_samples) 294 return self._notify_samples 295 296 def _sample_rate(self) -> pint.Quantity: 297 """ 298 Get the sample rate of the card 299 300 Returns 301 ------- 302 pint.Quantity 303 the sample rate of the card in Hz as a pint quantity 304 """ 305 return self.card.get_i(SPC_SAMPLERATE) * units.Hz 306 307 def memory_size(self, memory_samples : int = None) -> int: 308 """ 309 Sets the memory size in samples per channel. The memory size setting must be set before transferring 310 data to the card. (see register `SPC_MEMSIZE` in the manual) 311 312 Parameters 313 ---------- 314 memory_samples : int | pint.Quantity 315 the size of the memory in samples 316 317 Returns 318 ------- 319 int 320 the size of the memory in samples 321 """ 322 323 if memory_samples is not None: 324 memory_samples = UnitConversion.convert(memory_samples, units.Sa, int) 325 self.card.set_i(SPC_MEMSIZE, memory_samples) 326 self._memory_size = self.card.get_i(SPC_MEMSIZE) 327 return self._memory_size 328 329 def output_buffer_size(self, buffer_samples : int = None) -> int: 330 """ 331 Set the size of the output buffer (see register `SPC_DATA_OUTBUFSIZE` in the manual) 332 333 Parameters 334 ---------- 335 buffer_samples : int | pint.Quantity 336 the size of the output buffer in Bytes 337 338 Returns 339 ------- 340 int 341 the size of the output buffer in Samples 342 """ 343 344 if buffer_samples is not None: 345 buffer_samples = UnitConversion.convert(buffer_samples, units.B, int) 346 buffer_size = self.samples_to_bytes(buffer_samples) 347 self.card.set_i(SPC_DATA_OUTBUFSIZE, buffer_size) 348 return self.bytes_to_samples(self.card.get_i(SPC_DATA_OUTBUFSIZE)) 349 350 def loops(self, loops : int = None) -> int: 351 return self.card.loops(loops) 352 353 def pre_trigger(self, num_samples : int = None) -> int: 354 """ 355 Set the number of pre trigger samples (see register `SPC_PRETRIGGER` in the manual) 356 357 Parameters 358 ---------- 359 num_samples : int | pint.Quantity 360 the number of pre trigger samples 361 362 Returns 363 ------- 364 int 365 the number of pre trigger samples 366 """ 367 368 if num_samples is not None: 369 num_samples = UnitConversion.convert(num_samples, units.Sa, int) 370 self.card.set_i(SPC_PRETRIGGER, num_samples) 371 self._pre_trigger = self.card.get_i(SPC_PRETRIGGER) 372 return self._pre_trigger 373 374 def post_trigger(self, num_samples : int = None, set_pre_trigger : bool = True) -> int: 375 """ 376 Set the number of post trigger samples (see register `SPC_POSTTRIGGER` in the manual) 377 378 Parameters 379 ---------- 380 num_samples : int | pint.Quantity 381 the number of post trigger samples 382 set_pre_trigger : bool = True 383 if True, the pre trigger samples are set to the memory size minus the post trigger samples. 384 385 Returns 386 ------- 387 int 388 the number of post trigger samples 389 """ 390 391 if set_pre_trigger and self._memory_size < num_samples: 392 raise ValueError(f"The number of post trigger samples needs to be smaller than the total number of samples: {self._memory_size} < {num_samples}") 393 if num_samples is not None: 394 num_samples = UnitConversion.convert(num_samples, units.Sa, int) 395 self.card.set_i(SPC_POSTTRIGGER, num_samples) 396 post_trigger = self.card.get_i(SPC_POSTTRIGGER) 397 if set_pre_trigger: 398 self._pre_trigger = self._memory_size - post_trigger 399 return post_trigger 400 401 def allocate_buffer(self, num_samples : int, no_reshape = False) -> None: 402 """ 403 Memory allocation for the buffer that is used for communicating with the card 404 405 Parameters 406 ---------- 407 num_samples : int | pint.Quantity = None 408 use the number of samples an get the number of active channels and bytes per samples directly from the card 409 no_reshape : bool = False 410 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. 411 As well as for 12bit cards where three data points are packed in four bytes or two 16-bit samples. 412 """ 413 414 self.buffer_samples = UnitConversion.convert(num_samples, units.Sa, int) 415 no_reshape |= (self.bits_per_sample == 1) 416 self.buffer = self._allocate_buffer(self.buffer_samples, no_reshape, self.num_channels) 417 if self.bits_per_sample == 1: 418 self.unpackbits() # allocate the bit buffer for digital cards 419 420 421 def _allocate_buffer(self, num_samples : int, no_reshape : bool = False, num_channels : int = 1) -> npt.NDArray: 422 """ 423 Memory allocation for the buffer that is used for communicating with the card 424 425 Parameters 426 ---------- 427 num_samples : int | pint.Quantity = None 428 use the number of samples an get the number of active channels and bytes per samples directly from the card 429 no_reshape : bool = False 430 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. 431 432 Returns 433 ------- 434 numpy array 435 the allocated buffer 436 """ 437 438 buffer_size = self.samples_to_bytes(num_samples) 439 sample_type = self.numpy_type() 440 441 dwMask = self._buffer_alignment - 1 442 443 item_size = sample_type(0).itemsize 444 # allocate a buffer (numpy array) for DMA transfer: a little bigger one to have room for address alignment 445 databuffer_unaligned = np.empty(((self._buffer_alignment + buffer_size) // item_size, ), dtype = sample_type) # byte count to sample (// = integer division) 446 # two numpy-arrays may share the same memory: skip the begin up to the alignment boundary (ArrayVariable[SKIP_VALUE:]) 447 # Address of data-memory from numpy-array: ArrayVariable.__array_interface__['data'][0] 448 start_pos_samples = ((self._buffer_alignment - (databuffer_unaligned.__array_interface__['data'][0] & dwMask)) // item_size) 449 buffer = databuffer_unaligned[start_pos_samples:start_pos_samples + (buffer_size // item_size)] # byte address to sample size 450 if not no_reshape: 451 buffer = buffer.reshape((num_channels, -1), order='F')# index definition: [channel, sample] 452 return buffer 453 454 def _pre_buffer_transfer(self, *args, buffer_type=SPCM_BUF_DATA, direction=None, notify_samples=None, transfer_offset=None, transfer_length=None, exception_num_samples=True) -> None: 455 """ 456 Preparation before the transfer definition 457 458 Parameters 459 ---------- 460 *args : list 461 list of additional arguments that are added as flags to the start dma command 462 buffer_type : int 463 the type of buffer that is used for the transfer 464 direction : int 465 the direction of the transfer 466 notify_samples : int 467 the number of samples to notify the user about 468 transfer_offset : int 469 the offset of the transfer 470 transfer_length : int 471 the length of the transfer 472 exception_num_samples : bool 473 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 correctly with the number of samples being a multiple of the notify samples. 474 475 Raises 476 ------ 477 SpcmException 478 """ 479 480 self.notify_samples(UnitConversion.convert(notify_samples, units.Sa, int)) 481 transfer_offset = UnitConversion.convert(transfer_offset, units.Sa, int) 482 transfer_length = UnitConversion.convert(transfer_length, units.Sa, int) 483 484 if transfer_length is not None: 485 self.buffer_samples = transfer_length 486 487 if self.buffer is None: 488 raise SpcmException(text="No buffer defined for transfer") 489 if buffer_type: 490 self.buffer_type = buffer_type 491 if direction is None: 492 if self.direction == Direction.Acquisition: 493 self._direction = SPCM_DIR_CARDTOPC 494 elif self.direction == Direction.Generation: 495 self._direction = SPCM_DIR_PCTOCARD 496 else: 497 raise SpcmException(text="Please define a direction for transfer (SPCM_DIR_CARDTOPC or SPCM_DIR_PCTOCARD)") 498 else: 499 if direction == Direction.Acquisition: 500 self._direction = SPCM_DIR_CARDTOPC 501 elif direction == Direction.Generation: 502 self._direction = SPCM_DIR_PCTOCARD 503 else: 504 raise SpcmException(text="Please define a direction for transfer (SPCM_DIR_CARDTOPC or SPCM_DIR_PCTOCARD)") 505 506 if self._notify_samples != 0 and np.remainder(self.buffer_samples, self._notify_samples) and exception_num_samples: 507 raise SpcmException("The number of samples needs to be a multiple of the notify samples.") 508 509 if transfer_offset: 510 self.transfer_offset = self.samples_to_bytes(transfer_offset) 511 else: 512 self.transfer_offset = 0 513 514 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: 515 """ 516 Start the transfer of the data to or from the card (see the API function `spcm_dwDefTransfer_i64` in the manual) 517 518 Parameters 519 ---------- 520 *args : list 521 list of additonal arguments that are added as flags to the start dma command 522 buffer_type : int 523 the type of buffer that is used for the transfer 524 direction : int 525 the direction of the transfer 526 notify_samples : int 527 the number of samples to notify the user about 528 transfer_offset : int 529 the offset of the transfer 530 transfer_length : int 531 the length of the transfer 532 exception_num_samples : bool 533 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. 534 535 Raises 536 ------ 537 SpcmException 538 """ 539 540 self._pre_buffer_transfer(*args, buffer_type=buffer_type, direction=direction, notify_samples=notify_samples, transfer_offset=transfer_offset, transfer_length=transfer_length, exception_num_samples=exception_num_samples) 541 542 # we define the buffer for transfer 543 self.card._print("Starting the DMA transfer and waiting until data is in board memory") 544 self._c_buffer = self.buffer.ctypes.data_as(c_void_p) 545 self.card._check_error(spcm_dwDefTransfer_i64(self.card._handle, self.buffer_type, self._direction, self.notify_size, self._c_buffer, self.transfer_offset, self.buffer_size)) 546 547 self._post_buffer_transfer(*args, buffer_type=buffer_type, direction=direction, notify_samples=notify_samples, transfer_offset=transfer_offset, transfer_length=transfer_length, exception_num_samples=exception_num_samples) 548 549 def _post_buffer_transfer(self, *args, **kwargs) -> None: 550 """ 551 Post processing after the transfer definition 552 553 Parameters 554 ---------- 555 *args : list 556 list of additonal arguments that are added as flags to the start dma command 557 **kwargs : dict 558 dictionary of additional keyword arguments 559 """ 560 561 # Execute additional commands if available 562 if args: 563 cmd = 0 564 for arg in args: 565 cmd |= arg 566 self.card.cmd(cmd) 567 self.card._print("... data transfer started") 568 569 def duration(self, duration : pint.Quantity, pre_trigger_duration : pint.Quantity = None, post_trigger_duration : pint.Quantity = None) -> None: 570 """ 571 Set the duration of the data transfer 572 573 Parameters 574 ---------- 575 duration : pint.Quantity 576 the duration of the data transfer 577 pre_trigger_duration : pint.Quantity = None 578 the duration before the trigger event 579 post_trigger_duration : pint.Quantity = None 580 the duration after the trigger event 581 582 Returns 583 ------- 584 pint.Quantity 585 the duration of the data transfer 586 """ 587 588 if pre_trigger_duration is None and post_trigger_duration is None: 589 raise ValueError("Please define either pre_trigger_duration or post_trigger_duration") 590 591 memsize_min = self.card.get_i(SPC_AVAILMEMSIZE_MIN) 592 memsize_max = self.card.get_i(SPC_AVAILMEMSIZE_MAX) 593 memsize_stp = self.card.get_i(SPC_AVAILMEMSIZE_STEP) 594 num_samples = (duration * self._sample_rate()).to_base_units().magnitude 595 num_samples = np.ceil(num_samples / memsize_stp) * memsize_stp 596 num_samples = np.clip(num_samples, memsize_min, memsize_max) 597 num_samples = int(num_samples) 598 self.memory_size(num_samples) 599 self.allocate_buffer(num_samples) 600 if pre_trigger_duration is not None: 601 pre_min = self.card.get_i(SPC_AVAILPRETRIGGER_MIN) 602 pre_max = self.card.get_i(SPC_AVAILPRETRIGGER_MAX) 603 pre_stp = self.card.get_i(SPC_AVAILPRETRIGGER_STEP) 604 pre_samples = (pre_trigger_duration * self._sample_rate()).to_base_units().magnitude 605 pre_samples = np.ceil(pre_samples / pre_stp) * pre_stp 606 pre_samples = np.clip(pre_samples, pre_min, pre_max) 607 pre_samples = int(post_samples) 608 self.post_trigger(post_samples) 609 if post_trigger_duration is not None: 610 post_min = self.card.get_i(SPC_AVAILPOSTTRIGGER_MIN) 611 post_max = self.card.get_i(SPC_AVAILPOSTTRIGGER_MAX) 612 post_stp = self.card.get_i(SPC_AVAILPOSTTRIGGER_STEP) 613 post_samples = (post_trigger_duration * self._sample_rate()).to_base_units().magnitude 614 post_samples = np.ceil(post_samples / post_stp) * post_stp 615 post_samples = np.clip(post_samples, post_min, post_max) 616 post_samples = int(post_samples) 617 self.post_trigger(post_samples) 618 return num_samples, post_samples 619 620 def time_data(self, total_num_samples : int = None, return_units = units.s) -> npt.NDArray: 621 """ 622 Get the time array for the data buffer 623 624 Parameters 625 ---------- 626 total_num_samples : int | pint.Quantity 627 the total number of samples 628 return_units : pint.Quantity 629 the units that the time should be converted to 630 631 Returns 632 ------- 633 numpy array 634 the time array 635 """ 636 637 if total_num_samples is None: 638 total_num_samples = self._buffer_samples 639 total_num_samples = UnitConversion.convert(total_num_samples, units.Sa, int) 640 pre_trigger = UnitConversion.convert(self._pre_trigger, units.Sa, int) 641 return self.convert_time((np.arange(total_num_samples) - pre_trigger)).to(return_units) 642 643 def convert_time(self, time, return_units = units.s): 644 """ 645 Convert a time to the units of the card sample rate 646 647 Parameters 648 ---------- 649 time : numpy array 650 the time array with integers that should be converted 651 return_units : numpy array with pint.Quantity 652 the units that the time should be converted to 653 654 Returns 655 ------- 656 pint.Quantity 657 the converted time 658 """ 659 660 sample_rate = self._sample_rate() 661 return (time / sample_rate).to(return_units) 662 663 def unpack_12bit_buffer(self, data : npt.NDArray[np.int_] = None) -> npt.NDArray[np.int_]: 664 """ 665 Unpacks the 12-bit packed data to 16-bit data 666 667 Parameters 668 ---------- 669 data : numpy array 670 the packed data 671 672 Returns 673 ------- 674 numpy array 675 the unpacked 16bit buffer 676 """ 677 678 if not self._12bit_mode: 679 raise SpcmException("The card is not in 12bit packed mode") 680 681 if data is None: 682 data = self.buffer 683 684 fst_int8, mid_int8, lst_int8 = np.reshape(data, (data.shape[0] // 3, 3)).astype(np.int16).T 685 nibble_h = (mid_int8 >> 0) & 0x0F 686 nibble_m = (fst_int8 >> 4) & 0x0F 687 nibble_l = (fst_int8 >> 0) & 0x0F 688 fst_int12 = ((nibble_h << 12) >> 4) | (nibble_m << 4) | (nibble_l << 0) 689 nibble_h = (lst_int8 >> 4) & 0x0F 690 nibble_m = (lst_int8 >> 0) & 0x0F 691 nibble_l = (mid_int8 >> 4) & 0x0F 692 snd_int12 = ((nibble_h << 12) >> 4) | (nibble_m << 4) | (nibble_l << 0) 693 data_int12 = np.concatenate((fst_int12[:, None], snd_int12[:, None]), axis=1).reshape((-1,)) 694 data_int12 = data_int12.reshape((self.num_channels, self._buffer_samples), order='F') 695 return data_int12 696 697 def unpackbits(self, data : npt.NDArray[np.int_] = None) -> npt.NDArray[np.int_]: 698 """ 699 Unpack the buffer to bits 700 701 Parameters 702 ---------- 703 data : numpy array | None = None 704 the packed data 705 706 Returns 707 ------- 708 numpy array 709 the unpacked buffer 710 """ 711 712 if data is None: 713 data = self.buffer 714 dshape = list(data.shape) 715 return_data = data.reshape([-1, 1]) 716 num_bits = return_data.dtype.itemsize * 8 717 mask = 2**np.arange(num_bits, dtype=return_data.dtype).reshape([1, num_bits]) 718 self.bit_buffer = (return_data & mask).astype(np.bool).astype(np.uint8).reshape(dshape + [num_bits]) 719 return self.bit_buffer 720 721 def packbits(self) -> None: 722 """ 723 Pack the self.buffer from the self.bit_buffer 724 """ 725 726 self.buffer[:] = np.packbits(self._bit_buffer, axis=-1, bitorder='little').view(self.buffer.dtype).reshape(self.buffer.shape) 727 728 def tofile(self, filename : str, buffer = None, **kwargs) -> None: 729 """ 730 Export the buffer to a file. The file format is determined by the file extension 731 Supported file formats are: 732 * .bin: raw binary file 733 * .csv: comma-separated values file 734 * .npy: numpy binary file 735 * .npz: compressed numpy binary file 736 * .txt: whitespace-delimited text file 737 * .h5: hdf5 file format 738 739 Parameters 740 ---------- 741 filename : str 742 the name of the file that the buffer should be exported to 743 744 Raises 745 ------ 746 ImportError 747 if the file format is not supported 748 """ 749 750 if buffer is None: 751 buffer = self.buffer 752 file_path = Path(filename) 753 if file_path.suffix == '.bin': 754 buffer.tofile(file_path) 755 elif file_path.suffix == '.csv': 756 delimiter = kwargs.get('delimiter', ',') 757 np.savetxt(file_path, buffer, delimiter=delimiter) 758 elif file_path.suffix == '.npy': 759 np.save(file_path, buffer) 760 elif file_path.suffix == '.npz': 761 np.savez_compressed(file_path, buffer) 762 elif file_path.suffix == '.txt': 763 np.savetxt(file_path, buffer, fmt='%d') 764 elif file_path.suffix == '.h5' or file_path.suffix == '.hdf5': 765 import h5py 766 with h5py.File(file_path, 'w') as f: 767 f.create_dataset('data', data=buffer) 768 else: 769 raise ImportError("File format not supported") 770 771 def fromfile(self, filename : str, in_buffer : bool = True, **kwargs) -> npt.NDArray[np.int_]: 772 """ 773 Import the buffer from a file. The file format is determined by the file extension 774 Supported file formats are: 775 * .bin: raw binary file 776 * .csv: comma-separated values file 777 * .npy: numpy binary file 778 * .npz: compressed numpy binary file 779 * .txt: whitespace-delimited text file 780 * .h5: hdf5 file format 781 782 Parameters 783 ---------- 784 filename : str 785 the name of the file that the buffer should be imported from 786 787 Raises 788 ------ 789 ImportError 790 if the file format is not supported 791 """ 792 793 file_path = Path(filename) 794 if file_path.suffix == '.bin': 795 dtype = kwargs.get('dtype', self.numpy_type()) 796 shape = kwargs.get('shape', (self.num_channels, self.buffer_size // self.num_channels)) 797 buffer = np.fromfile(file_path, dtype=dtype) 798 loaded_data = buffer.reshape(shape, order='C') 799 elif file_path.suffix == '.csv': 800 delimiter = kwargs.get('delimiter', ',') 801 loaded_data = np.loadtxt(file_path, delimiter=delimiter) 802 elif file_path.suffix == '.npy': 803 loaded_data = np.load(file_path) 804 elif file_path.suffix == '.npz': 805 data = np.load(file_path) 806 loaded_data = data['arr_0'] 807 elif file_path.suffix == '.txt': 808 loaded_data = np.loadtxt(file_path) 809 elif file_path.suffix == '.h5' or file_path.suffix == '.hdf5': 810 import h5py 811 with h5py.File(file_path, 'r') as f: 812 loaded_data = f['data'][()] 813 else: 814 raise ImportError("File format not supported") 815 816 if in_buffer: 817 self.buffer[:] = loaded_data 818 return loaded_data 819 820 821 def avail_card_len(self, available_samples : int = 0) -> None: 822 """ 823 Set the amount of data that has been read out of the data buffer (see register `SPC_DATA_AVAIL_CARD_LEN` in the manual) 824 825 Parameters 826 ---------- 827 available_samples : int | pint.Quantity 828 the amount of data that is available for reading 829 """ 830 831 available_samples = UnitConversion.convert(available_samples, units.Sa, int) 832 available_bytes = self.samples_to_bytes(available_samples) 833 self.card.set_i(SPC_DATA_AVAIL_CARD_LEN, available_bytes) 834 835 def avail_user_pos(self, in_bytes : bool = False) -> int: 836 """ 837 Get the current position of the pointer in the data buffer (see register `SPC_DATA_AVAIL_USER_POS` in the manual) 838 839 Parameters 840 ---------- 841 in_bytes : bool 842 if True, the position is returned in bytes 843 844 Returns 845 ------- 846 int 847 pointer position 848 """ 849 850 self.current_user_pos = self.card.get_i(SPC_DATA_AVAIL_USER_POS) 851 if not in_bytes: 852 self.current_user_pos = self.bytes_to_samples(self.current_user_pos) 853 return self.current_user_pos 854 855 def avail_user_len(self, in_bytes : bool = False) -> int: 856 """ 857 Get the current length of the data in the data buffer (see register `SPC_DATA_AVAIL_USER_LEN` in the manual) 858 859 Parameters 860 ---------- 861 in_bytes : bool 862 if True, the length is returned in bytes 863 864 Returns 865 ------- 866 int 867 data length available 868 """ 869 870 user_len = self.card.get_i(SPC_DATA_AVAIL_USER_LEN) 871 if not in_bytes: 872 user_len = self.bytes_to_samples(user_len) 873 return user_len 874 875 def fill_size_promille(self, return_unit = None) -> int: 876 """ 877 Get the fill size of the data buffer (see register `SPC_FILLSIZEPROMILLE` in the manual) 878 879 Returns 880 ------- 881 int 882 fill size 883 """ 884 885 return_value = self.card.get_i(SPC_FILLSIZEPROMILLE) 886 if return_unit is not None: return_value = UnitConversion.to_unit(return_value * units.promille, return_unit) 887 return return_value 888 889 def wait_dma(self) -> None: 890 """ 891 Wait for the DMA transfer to finish (see register `M2CMD_DATA_WAITDMA` in the manual) 892 """ 893 894 self.card.cmd(M2CMD_DATA_WAITDMA) 895 wait = wait_dma 896 897 def numpy_type(self) -> npt.NDArray[np.int_]: 898 """ 899 Get the type of numpy data from number of bytes 900 901 Returns 902 ------- 903 numpy data type 904 the type of data that is used by the card 905 """ 906 907 if self._8bit_mode: 908 return np.uint8 909 if self._12bit_mode: 910 return np.int8 911 if self.bits_per_sample == 1: 912 if self.num_channels <= 8: 913 return np.uint8 914 elif self.num_channels <= 16: 915 return np.uint16 916 elif self.num_channels <= 32: 917 return np.uint32 918 return np.uint64 919 if self.bits_per_sample <= 8: 920 return np.int8 921 elif self.bits_per_sample <= 16: 922 return np.int16 923 elif self.bits_per_sample <= 32: 924 return np.int32 925 return np.int64 926 927 # Data conversion mode 928 def data_conversion(self, mode : int = None, unpack : bool = False) -> int: 929 """ 930 Set the data conversion mode (see register `SPC_DATACONVERSION` in the manual) 931 932 Parameters 933 ---------- 934 mode : int 935 the data conversion mode 936 unpack : bool = False 937 if True, the buffer is automatically unpacked if the mode is 12bit packed (WARNING: this might slow down the processing) 938 """ 939 940 if mode is not None: 941 self.card.set_i(SPC_DATACONVERSION, mode) 942 mode = self.card.get_i(SPC_DATACONVERSION) 943 self._8bit_mode = (mode == SPCM_DC_12BIT_TO_8BIT or mode == SPCM_DC_14BIT_TO_8BIT or mode == SPCM_DC_16BIT_TO_8BIT) 944 self._12bit_mode = (mode == SPCM_DC_12BIT_TO_12BITPACKED) 945 self._unpack_12bit = unpack 946 self._bits_per_sample() 947 self._bytes_per_sample() 948 return mode 949 950 def avail_data_conversion(self) -> int: 951 """ 952 Get the available data conversion modes (see register `SPC_AVAILDATACONVERSION` in the manual) 953 954 Returns 955 ------- 956 int 957 the available data conversion modes 958 """ 959 return self.card.get_i(SPC_AVAILDATACONVERSION) 960 961 # Iterator methods 962 963 iterator_index = 0 964 _max_timeout = 64 965 966 _to_transfer_samples = 0 967 _current_samples = 0 968 969 _verbose = False 970 971 def verbose(self, verbose : bool = None) -> bool: 972 """ 973 Set or get the verbose mode for the data transfer 974 975 Parameters 976 ---------- 977 verbose : bool = None 978 the verbose mode 979 """ 980 981 if verbose is not None: 982 self._verbose = verbose 983 return self._verbose 984 985 def to_transfer_samples(self, samples) -> None: 986 """ 987 This method sets the number of samples to transfer 988 989 Parameters 990 ---------- 991 samples : int | pint.Quantity 992 the number of samples to transfer 993 """ 994 995 samples = UnitConversion.convert(samples, units.Sa, int) 996 self._to_transfer_samples = samples 997 998 def __iter__(self): 999 """ 1000 This method is called when the iterator is initialized 1001 1002 Returns 1003 ------- 1004 DataIterator 1005 the iterator itself 1006 """ 1007 1008 self.iterator_index = 0 1009 return self 1010 1011 def polling(self, polling : bool = True, timer : float = 0.01) -> None: 1012 """ 1013 Set the polling mode for the data transfer otherwise wait_dma() is used 1014 1015 Parameters 1016 ---------- 1017 polling : bool 1018 True to enable polling, False to disable polling 1019 timer : float | pint.Quantity 1020 the polling timer in seconds 1021 """ 1022 1023 self._polling = polling 1024 self._polling_timer = UnitConversion.convert(timer, units.s, float, rounding=None) 1025 1026 _auto_avail_card_len = True 1027 def auto_avail_card_len(self, value : bool = None) -> bool: 1028 """ 1029 Enable or disable the automatic sending of the number of samples that the card can now use for sample data transfer again 1030 1031 Parameters 1032 ---------- 1033 value : bool = None 1034 True to enable, False to disable and None to get the current status 1035 1036 Returns 1037 ------- 1038 bool 1039 the current status 1040 """ 1041 if value is not None: 1042 self._auto_avail_card_len = value 1043 return self._auto_avail_card_len 1044 1045 def __next__(self) -> npt.ArrayLike: 1046 """ 1047 This method is called when the next element is requested from the iterator 1048 1049 Returns 1050 ------- 1051 npt.ArrayLike 1052 the next data block 1053 1054 Raises 1055 ------ 1056 StopIteration 1057 """ 1058 timeout_counter = 0 1059 # notify the card that data is available or read, but only after the first block 1060 if self.iterator_index != 0 and self._auto_avail_card_len: 1061 self.flush() 1062 1063 _notify_samples = self._notify_samples 1064 if self._12bit_mode: 1065 _notify_samples = (_notify_samples * 3) // 2 # in 12bit packed mode three bytes are used for two samples 1066 1067 while True: 1068 try: 1069 if not self._polling: 1070 self.wait_dma() 1071 else: 1072 user_len = self.avail_user_len() 1073 if user_len >= _notify_samples: 1074 break 1075 time.sleep(self._polling_timer) 1076 except SpcmTimeout: 1077 self.card._print("... Timeout ({})".format(timeout_counter), end='\r') 1078 timeout_counter += 1 1079 if timeout_counter > self._max_timeout: 1080 self.iterator_index = 0 1081 raise StopIteration 1082 else: 1083 if not self._polling: 1084 break 1085 1086 self.iterator_index += 1 1087 1088 self._current_samples += _notify_samples 1089 if self._to_transfer_samples != 0 and self._to_transfer_samples < self._current_samples: 1090 self.iterator_index = 0 1091 raise StopIteration 1092 1093 user_pos = self.avail_user_pos() 1094 fill_size = self.fill_size_promille() 1095 1096 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) 1097 1098 if self._unpack and self._12bit_mode: 1099 return self.unpack_12bit_buffer(self.buffer[:, user_pos:user_pos+_notify_samples]) 1100 return self.buffer[:, user_pos:user_pos+_notify_samples] 1101 1102 def flush(self): 1103 """ 1104 This method is used to tell the card that a notify size of data is freed up after reading (acquisition) or written to (generation) 1105 """ 1106 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 bufferbuffer_size(int): defines the size of the current buffer shared between the PC and the cardbuffer_type(int): defines the type of data in the buffer that is used for the transfernum_channels(int): defines the number of channels that are used for the transferbytes_per_sample(int): defines the number of bytes per samplebits_per_sample(int): defines the number of bits per sampledirection(Direction = Direction.Acquisition): Direction of the data transfer.
86 def __init__(self, card, *args, **kwargs) -> None: 87 """ 88 Initialize the DataTransfer object with a card object and additional arguments 89 90 Parameters 91 ---------- 92 card : Card 93 the card object that is used for the data transfer 94 *args : list 95 list of additional arguments 96 **kwargs : dict 97 dictionary of additional keyword arguments 98 """ 99 100 self.transfer_offset = 0 101 self.buffer_size = 0 102 self.notify_size = 0 103 self.num_channels = 0 104 self.bytes_per_sample = 0 105 self.bits_per_sample = 0 106 107 self.current_user_pos = 0 108 109 self._buffer_samples = 0 110 self._notify_samples = 0 111 self._memory_size = 0 112 self._c_buffer = None 113 self._buffer_alignment = 4096 114 self._np_buffer = None 115 self._bit_buffer = None 116 self._8bit_mode = False 117 self._12bit_mode = False 118 self._pre_trigger = 0 119 120 super().__init__(card, *args, **kwargs) 121 self.buffer_type = SPCM_BUF_DATA 122 self._bytes_per_sample() 123 self._bits_per_sample() 124 self.num_channels = self.card.active_channels() 125 126 # Find out the direction of transfer 127 if self.function_type == SPCM_TYPE_AI or self.function_type == SPCM_TYPE_DI: 128 self.direction = Direction.Acquisition 129 self._direction = SPCM_DIR_CARDTOPC 130 elif self.function_type == SPCM_TYPE_AO or self.function_type == SPCM_TYPE_DO: 131 self.direction = Direction.Generation 132 self._direction = SPCM_DIR_PCTOCARD 133 else: 134 self.direction = Direction.Undefined 135 self._direction = 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
138 @property 139 def buffer(self) -> npt.NDArray[np.int_]: 140 """ 141 The numpy buffer object that interfaces the Card and can be written and read from 142 143 Returns 144 ------- 145 numpy array 146 the numpy buffer object with the following array index definition: 147 `[channel, sample]` 148 or in case of multiple recording / replay: 149 `[segment, sample, channel]` 150 """ 151 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]
161 @property 162 def bit_buffer(self) -> npt.NDArray[np.int_]: 163 """ 164 The bit buffer object that interfaces the Card and can be written and read from 165 166 Returns 167 ------- 168 numpy array 169 with the buffer object where all the individual bits are now unpacked 170 """ 171 172 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
182 @property 183 def buffer_samples(self) -> int: 184 """ 185 The number of samples in the buffer 186 187 Returns 188 ------- 189 int 190 the number of samples in the buffer 191 """ 192 return self._buffer_samples
The number of samples in the buffer
Returns
- int: the number of samples in the buffer
238 def bytes_to_samples(self, num_bytes : int) -> int: 239 """ 240 Convert bytes to samples 241 242 Parameters 243 ---------- 244 bytes : int 245 the number of bytes 246 247 Returns 248 ------- 249 int 250 the number of samples 251 """ 252 253 if self.bits_per_sample > 1: 254 num_samples = int(num_bytes // self.bytes_per_sample // self.num_channels) 255 else: 256 num_samples = int(num_bytes // self.num_channels * 8) 257 return num_samples
Convert bytes to samples
Parameters
- bytes (int): the number of bytes
Returns
- int: the number of samples
259 def samples_to_bytes(self, num_samples : int) -> int: 260 """ 261 Convert samples to bytes 262 263 Parameters 264 ---------- 265 num_samples : int 266 the number of samples 267 268 Returns 269 ------- 270 int 271 the number of bytes 272 """ 273 274 if self.bits_per_sample > 1: 275 num_bytes = int(num_samples * self.bytes_per_sample * self.num_channels) 276 else: 277 num_bytes = int(num_samples * self.num_channels // 8) 278 return num_bytes
Convert samples to bytes
Parameters
- num_samples (int): the number of samples
Returns
- int: the number of bytes
280 def notify_samples(self, notify_samples : int = None) -> int: 281 """ 282 Set the number of samples to notify the user about 283 284 Parameters 285 ---------- 286 notify_samples : int | pint.Quantity 287 the number of samples to notify the user about 288 """ 289 290 if notify_samples is not None: 291 notify_samples = UnitConversion.convert(notify_samples, units.Sa, int) 292 self._notify_samples = notify_samples 293 self.notify_size = self.samples_to_bytes(self._notify_samples) 294 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
307 def memory_size(self, memory_samples : int = None) -> int: 308 """ 309 Sets the memory size in samples per channel. The memory size setting must be set before transferring 310 data to the card. (see register `SPC_MEMSIZE` in the manual) 311 312 Parameters 313 ---------- 314 memory_samples : int | pint.Quantity 315 the size of the memory in samples 316 317 Returns 318 ------- 319 int 320 the size of the memory in samples 321 """ 322 323 if memory_samples is not None: 324 memory_samples = UnitConversion.convert(memory_samples, units.Sa, int) 325 self.card.set_i(SPC_MEMSIZE, memory_samples) 326 self._memory_size = self.card.get_i(SPC_MEMSIZE) 327 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
329 def output_buffer_size(self, buffer_samples : int = None) -> int: 330 """ 331 Set the size of the output buffer (see register `SPC_DATA_OUTBUFSIZE` in the manual) 332 333 Parameters 334 ---------- 335 buffer_samples : int | pint.Quantity 336 the size of the output buffer in Bytes 337 338 Returns 339 ------- 340 int 341 the size of the output buffer in Samples 342 """ 343 344 if buffer_samples is not None: 345 buffer_samples = UnitConversion.convert(buffer_samples, units.B, int) 346 buffer_size = self.samples_to_bytes(buffer_samples) 347 self.card.set_i(SPC_DATA_OUTBUFSIZE, buffer_size) 348 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
353 def pre_trigger(self, num_samples : int = None) -> int: 354 """ 355 Set the number of pre trigger samples (see register `SPC_PRETRIGGER` in the manual) 356 357 Parameters 358 ---------- 359 num_samples : int | pint.Quantity 360 the number of pre trigger samples 361 362 Returns 363 ------- 364 int 365 the number of pre trigger samples 366 """ 367 368 if num_samples is not None: 369 num_samples = UnitConversion.convert(num_samples, units.Sa, int) 370 self.card.set_i(SPC_PRETRIGGER, num_samples) 371 self._pre_trigger = self.card.get_i(SPC_PRETRIGGER) 372 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
374 def post_trigger(self, num_samples : int = None, set_pre_trigger : bool = True) -> int: 375 """ 376 Set the number of post trigger samples (see register `SPC_POSTTRIGGER` in the manual) 377 378 Parameters 379 ---------- 380 num_samples : int | pint.Quantity 381 the number of post trigger samples 382 set_pre_trigger : bool = True 383 if True, the pre trigger samples are set to the memory size minus the post trigger samples. 384 385 Returns 386 ------- 387 int 388 the number of post trigger samples 389 """ 390 391 if set_pre_trigger and self._memory_size < num_samples: 392 raise ValueError(f"The number of post trigger samples needs to be smaller than the total number of samples: {self._memory_size} < {num_samples}") 393 if num_samples is not None: 394 num_samples = UnitConversion.convert(num_samples, units.Sa, int) 395 self.card.set_i(SPC_POSTTRIGGER, num_samples) 396 post_trigger = self.card.get_i(SPC_POSTTRIGGER) 397 if set_pre_trigger: 398 self._pre_trigger = self._memory_size - post_trigger 399 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
401 def allocate_buffer(self, num_samples : int, no_reshape = False) -> None: 402 """ 403 Memory allocation for the buffer that is used for communicating with the card 404 405 Parameters 406 ---------- 407 num_samples : int | pint.Quantity = None 408 use the number of samples an get the number of active channels and bytes per samples directly from the card 409 no_reshape : bool = False 410 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. 411 As well as for 12bit cards where three data points are packed in four bytes or two 16-bit samples. 412 """ 413 414 self.buffer_samples = UnitConversion.convert(num_samples, units.Sa, int) 415 no_reshape |= (self.bits_per_sample == 1) 416 self.buffer = self._allocate_buffer(self.buffer_samples, no_reshape, self.num_channels) 417 if self.bits_per_sample == 1: 418 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.
514 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: 515 """ 516 Start the transfer of the data to or from the card (see the API function `spcm_dwDefTransfer_i64` in the manual) 517 518 Parameters 519 ---------- 520 *args : list 521 list of additonal arguments that are added as flags to the start dma command 522 buffer_type : int 523 the type of buffer that is used for the transfer 524 direction : int 525 the direction of the transfer 526 notify_samples : int 527 the number of samples to notify the user about 528 transfer_offset : int 529 the offset of the transfer 530 transfer_length : int 531 the length of the transfer 532 exception_num_samples : bool 533 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. 534 535 Raises 536 ------ 537 SpcmException 538 """ 539 540 self._pre_buffer_transfer(*args, buffer_type=buffer_type, direction=direction, notify_samples=notify_samples, transfer_offset=transfer_offset, transfer_length=transfer_length, exception_num_samples=exception_num_samples) 541 542 # we define the buffer for transfer 543 self.card._print("Starting the DMA transfer and waiting until data is in board memory") 544 self._c_buffer = self.buffer.ctypes.data_as(c_void_p) 545 self.card._check_error(spcm_dwDefTransfer_i64(self.card._handle, self.buffer_type, self._direction, self.notify_size, self._c_buffer, self.transfer_offset, self.buffer_size)) 546 547 self._post_buffer_transfer(*args, buffer_type=buffer_type, direction=direction, notify_samples=notify_samples, transfer_offset=transfer_offset, transfer_length=transfer_length, exception_num_samples=exception_num_samples)
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
569 def duration(self, duration : pint.Quantity, pre_trigger_duration : pint.Quantity = None, post_trigger_duration : pint.Quantity = None) -> None: 570 """ 571 Set the duration of the data transfer 572 573 Parameters 574 ---------- 575 duration : pint.Quantity 576 the duration of the data transfer 577 pre_trigger_duration : pint.Quantity = None 578 the duration before the trigger event 579 post_trigger_duration : pint.Quantity = None 580 the duration after the trigger event 581 582 Returns 583 ------- 584 pint.Quantity 585 the duration of the data transfer 586 """ 587 588 if pre_trigger_duration is None and post_trigger_duration is None: 589 raise ValueError("Please define either pre_trigger_duration or post_trigger_duration") 590 591 memsize_min = self.card.get_i(SPC_AVAILMEMSIZE_MIN) 592 memsize_max = self.card.get_i(SPC_AVAILMEMSIZE_MAX) 593 memsize_stp = self.card.get_i(SPC_AVAILMEMSIZE_STEP) 594 num_samples = (duration * self._sample_rate()).to_base_units().magnitude 595 num_samples = np.ceil(num_samples / memsize_stp) * memsize_stp 596 num_samples = np.clip(num_samples, memsize_min, memsize_max) 597 num_samples = int(num_samples) 598 self.memory_size(num_samples) 599 self.allocate_buffer(num_samples) 600 if pre_trigger_duration is not None: 601 pre_min = self.card.get_i(SPC_AVAILPRETRIGGER_MIN) 602 pre_max = self.card.get_i(SPC_AVAILPRETRIGGER_MAX) 603 pre_stp = self.card.get_i(SPC_AVAILPRETRIGGER_STEP) 604 pre_samples = (pre_trigger_duration * self._sample_rate()).to_base_units().magnitude 605 pre_samples = np.ceil(pre_samples / pre_stp) * pre_stp 606 pre_samples = np.clip(pre_samples, pre_min, pre_max) 607 pre_samples = int(post_samples) 608 self.post_trigger(post_samples) 609 if post_trigger_duration is not None: 610 post_min = self.card.get_i(SPC_AVAILPOSTTRIGGER_MIN) 611 post_max = self.card.get_i(SPC_AVAILPOSTTRIGGER_MAX) 612 post_stp = self.card.get_i(SPC_AVAILPOSTTRIGGER_STEP) 613 post_samples = (post_trigger_duration * self._sample_rate()).to_base_units().magnitude 614 post_samples = np.ceil(post_samples / post_stp) * post_stp 615 post_samples = np.clip(post_samples, post_min, post_max) 616 post_samples = int(post_samples) 617 self.post_trigger(post_samples) 618 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
620 def time_data(self, total_num_samples : int = None, return_units = units.s) -> npt.NDArray: 621 """ 622 Get the time array for the data buffer 623 624 Parameters 625 ---------- 626 total_num_samples : int | pint.Quantity 627 the total number of samples 628 return_units : pint.Quantity 629 the units that the time should be converted to 630 631 Returns 632 ------- 633 numpy array 634 the time array 635 """ 636 637 if total_num_samples is None: 638 total_num_samples = self._buffer_samples 639 total_num_samples = UnitConversion.convert(total_num_samples, units.Sa, int) 640 pre_trigger = UnitConversion.convert(self._pre_trigger, units.Sa, int) 641 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
643 def convert_time(self, time, return_units = units.s): 644 """ 645 Convert a time to the units of the card sample rate 646 647 Parameters 648 ---------- 649 time : numpy array 650 the time array with integers that should be converted 651 return_units : numpy array with pint.Quantity 652 the units that the time should be converted to 653 654 Returns 655 ------- 656 pint.Quantity 657 the converted time 658 """ 659 660 sample_rate = self._sample_rate() 661 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
663 def unpack_12bit_buffer(self, data : npt.NDArray[np.int_] = None) -> npt.NDArray[np.int_]: 664 """ 665 Unpacks the 12-bit packed data to 16-bit data 666 667 Parameters 668 ---------- 669 data : numpy array 670 the packed data 671 672 Returns 673 ------- 674 numpy array 675 the unpacked 16bit buffer 676 """ 677 678 if not self._12bit_mode: 679 raise SpcmException("The card is not in 12bit packed mode") 680 681 if data is None: 682 data = self.buffer 683 684 fst_int8, mid_int8, lst_int8 = np.reshape(data, (data.shape[0] // 3, 3)).astype(np.int16).T 685 nibble_h = (mid_int8 >> 0) & 0x0F 686 nibble_m = (fst_int8 >> 4) & 0x0F 687 nibble_l = (fst_int8 >> 0) & 0x0F 688 fst_int12 = ((nibble_h << 12) >> 4) | (nibble_m << 4) | (nibble_l << 0) 689 nibble_h = (lst_int8 >> 4) & 0x0F 690 nibble_m = (lst_int8 >> 0) & 0x0F 691 nibble_l = (mid_int8 >> 4) & 0x0F 692 snd_int12 = ((nibble_h << 12) >> 4) | (nibble_m << 4) | (nibble_l << 0) 693 data_int12 = np.concatenate((fst_int12[:, None], snd_int12[:, None]), axis=1).reshape((-1,)) 694 data_int12 = data_int12.reshape((self.num_channels, self._buffer_samples), order='F') 695 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
697 def unpackbits(self, data : npt.NDArray[np.int_] = None) -> npt.NDArray[np.int_]: 698 """ 699 Unpack the buffer to bits 700 701 Parameters 702 ---------- 703 data : numpy array | None = None 704 the packed data 705 706 Returns 707 ------- 708 numpy array 709 the unpacked buffer 710 """ 711 712 if data is None: 713 data = self.buffer 714 dshape = list(data.shape) 715 return_data = data.reshape([-1, 1]) 716 num_bits = return_data.dtype.itemsize * 8 717 mask = 2**np.arange(num_bits, dtype=return_data.dtype).reshape([1, num_bits]) 718 self.bit_buffer = (return_data & mask).astype(np.bool).astype(np.uint8).reshape(dshape + [num_bits]) 719 return self.bit_buffer
Unpack the buffer to bits
Parameters
- data (numpy array | None = None): the packed data
Returns
- numpy array: the unpacked buffer
721 def packbits(self) -> None: 722 """ 723 Pack the self.buffer from the self.bit_buffer 724 """ 725 726 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
728 def tofile(self, filename : str, buffer = None, **kwargs) -> None: 729 """ 730 Export the buffer to a file. The file format is determined by the file extension 731 Supported file formats are: 732 * .bin: raw binary file 733 * .csv: comma-separated values file 734 * .npy: numpy binary file 735 * .npz: compressed numpy binary file 736 * .txt: whitespace-delimited text file 737 * .h5: hdf5 file format 738 739 Parameters 740 ---------- 741 filename : str 742 the name of the file that the buffer should be exported to 743 744 Raises 745 ------ 746 ImportError 747 if the file format is not supported 748 """ 749 750 if buffer is None: 751 buffer = self.buffer 752 file_path = Path(filename) 753 if file_path.suffix == '.bin': 754 buffer.tofile(file_path) 755 elif file_path.suffix == '.csv': 756 delimiter = kwargs.get('delimiter', ',') 757 np.savetxt(file_path, buffer, delimiter=delimiter) 758 elif file_path.suffix == '.npy': 759 np.save(file_path, buffer) 760 elif file_path.suffix == '.npz': 761 np.savez_compressed(file_path, buffer) 762 elif file_path.suffix == '.txt': 763 np.savetxt(file_path, buffer, fmt='%d') 764 elif file_path.suffix == '.h5' or file_path.suffix == '.hdf5': 765 import h5py 766 with h5py.File(file_path, 'w') as f: 767 f.create_dataset('data', data=buffer) 768 else: 769 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
771 def fromfile(self, filename : str, in_buffer : bool = True, **kwargs) -> npt.NDArray[np.int_]: 772 """ 773 Import the buffer from a file. The file format is determined by the file extension 774 Supported file formats are: 775 * .bin: raw binary file 776 * .csv: comma-separated values file 777 * .npy: numpy binary file 778 * .npz: compressed numpy binary file 779 * .txt: whitespace-delimited text file 780 * .h5: hdf5 file format 781 782 Parameters 783 ---------- 784 filename : str 785 the name of the file that the buffer should be imported from 786 787 Raises 788 ------ 789 ImportError 790 if the file format is not supported 791 """ 792 793 file_path = Path(filename) 794 if file_path.suffix == '.bin': 795 dtype = kwargs.get('dtype', self.numpy_type()) 796 shape = kwargs.get('shape', (self.num_channels, self.buffer_size // self.num_channels)) 797 buffer = np.fromfile(file_path, dtype=dtype) 798 loaded_data = buffer.reshape(shape, order='C') 799 elif file_path.suffix == '.csv': 800 delimiter = kwargs.get('delimiter', ',') 801 loaded_data = np.loadtxt(file_path, delimiter=delimiter) 802 elif file_path.suffix == '.npy': 803 loaded_data = np.load(file_path) 804 elif file_path.suffix == '.npz': 805 data = np.load(file_path) 806 loaded_data = data['arr_0'] 807 elif file_path.suffix == '.txt': 808 loaded_data = np.loadtxt(file_path) 809 elif file_path.suffix == '.h5' or file_path.suffix == '.hdf5': 810 import h5py 811 with h5py.File(file_path, 'r') as f: 812 loaded_data = f['data'][()] 813 else: 814 raise ImportError("File format not supported") 815 816 if in_buffer: 817 self.buffer[:] = loaded_data 818 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
821 def avail_card_len(self, available_samples : int = 0) -> None: 822 """ 823 Set the amount of data that has been read out of the data buffer (see register `SPC_DATA_AVAIL_CARD_LEN` in the manual) 824 825 Parameters 826 ---------- 827 available_samples : int | pint.Quantity 828 the amount of data that is available for reading 829 """ 830 831 available_samples = UnitConversion.convert(available_samples, units.Sa, int) 832 available_bytes = self.samples_to_bytes(available_samples) 833 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
835 def avail_user_pos(self, in_bytes : bool = False) -> int: 836 """ 837 Get the current position of the pointer in the data buffer (see register `SPC_DATA_AVAIL_USER_POS` in the manual) 838 839 Parameters 840 ---------- 841 in_bytes : bool 842 if True, the position is returned in bytes 843 844 Returns 845 ------- 846 int 847 pointer position 848 """ 849 850 self.current_user_pos = self.card.get_i(SPC_DATA_AVAIL_USER_POS) 851 if not in_bytes: 852 self.current_user_pos = self.bytes_to_samples(self.current_user_pos) 853 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
855 def avail_user_len(self, in_bytes : bool = False) -> int: 856 """ 857 Get the current length of the data in the data buffer (see register `SPC_DATA_AVAIL_USER_LEN` in the manual) 858 859 Parameters 860 ---------- 861 in_bytes : bool 862 if True, the length is returned in bytes 863 864 Returns 865 ------- 866 int 867 data length available 868 """ 869 870 user_len = self.card.get_i(SPC_DATA_AVAIL_USER_LEN) 871 if not in_bytes: 872 user_len = self.bytes_to_samples(user_len) 873 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
875 def fill_size_promille(self, return_unit = None) -> int: 876 """ 877 Get the fill size of the data buffer (see register `SPC_FILLSIZEPROMILLE` in the manual) 878 879 Returns 880 ------- 881 int 882 fill size 883 """ 884 885 return_value = self.card.get_i(SPC_FILLSIZEPROMILLE) 886 if return_unit is not None: return_value = UnitConversion.to_unit(return_value * units.promille, return_unit) 887 return return_value
Get the fill size of the data buffer (see register SPC_FILLSIZEPROMILLE in the manual)
Returns
- int: fill size
889 def wait_dma(self) -> None: 890 """ 891 Wait for the DMA transfer to finish (see register `M2CMD_DATA_WAITDMA` in the manual) 892 """ 893 894 self.card.cmd(M2CMD_DATA_WAITDMA)
Wait for the DMA transfer to finish (see register M2CMD_DATA_WAITDMA in the manual)
889 def wait_dma(self) -> None: 890 """ 891 Wait for the DMA transfer to finish (see register `M2CMD_DATA_WAITDMA` in the manual) 892 """ 893 894 self.card.cmd(M2CMD_DATA_WAITDMA)
Wait for the DMA transfer to finish (see register M2CMD_DATA_WAITDMA in the manual)
897 def numpy_type(self) -> npt.NDArray[np.int_]: 898 """ 899 Get the type of numpy data from number of bytes 900 901 Returns 902 ------- 903 numpy data type 904 the type of data that is used by the card 905 """ 906 907 if self._8bit_mode: 908 return np.uint8 909 if self._12bit_mode: 910 return np.int8 911 if self.bits_per_sample == 1: 912 if self.num_channels <= 8: 913 return np.uint8 914 elif self.num_channels <= 16: 915 return np.uint16 916 elif self.num_channels <= 32: 917 return np.uint32 918 return np.uint64 919 if self.bits_per_sample <= 8: 920 return np.int8 921 elif self.bits_per_sample <= 16: 922 return np.int16 923 elif self.bits_per_sample <= 32: 924 return np.int32 925 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
928 def data_conversion(self, mode : int = None, unpack : bool = False) -> int: 929 """ 930 Set the data conversion mode (see register `SPC_DATACONVERSION` in the manual) 931 932 Parameters 933 ---------- 934 mode : int 935 the data conversion mode 936 unpack : bool = False 937 if True, the buffer is automatically unpacked if the mode is 12bit packed (WARNING: this might slow down the processing) 938 """ 939 940 if mode is not None: 941 self.card.set_i(SPC_DATACONVERSION, mode) 942 mode = self.card.get_i(SPC_DATACONVERSION) 943 self._8bit_mode = (mode == SPCM_DC_12BIT_TO_8BIT or mode == SPCM_DC_14BIT_TO_8BIT or mode == SPCM_DC_16BIT_TO_8BIT) 944 self._12bit_mode = (mode == SPCM_DC_12BIT_TO_12BITPACKED) 945 self._unpack_12bit = unpack 946 self._bits_per_sample() 947 self._bytes_per_sample() 948 return mode
Set the data conversion mode (see register SPC_DATACONVERSION in the manual)
Parameters
- mode (int): the data conversion mode
- unpack (bool = False): if True, the buffer is automatically unpacked if the mode is 12bit packed (WARNING: this might slow down the processing)
950 def avail_data_conversion(self) -> int: 951 """ 952 Get the available data conversion modes (see register `SPC_AVAILDATACONVERSION` in the manual) 953 954 Returns 955 ------- 956 int 957 the available data conversion modes 958 """ 959 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
971 def verbose(self, verbose : bool = None) -> bool: 972 """ 973 Set or get the verbose mode for the data transfer 974 975 Parameters 976 ---------- 977 verbose : bool = None 978 the verbose mode 979 """ 980 981 if verbose is not None: 982 self._verbose = verbose 983 return self._verbose
Set or get the verbose mode for the data transfer
Parameters
- verbose (bool = None): the verbose mode
985 def to_transfer_samples(self, samples) -> None: 986 """ 987 This method sets the number of samples to transfer 988 989 Parameters 990 ---------- 991 samples : int | pint.Quantity 992 the number of samples to transfer 993 """ 994 995 samples = UnitConversion.convert(samples, units.Sa, int) 996 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
1011 def polling(self, polling : bool = True, timer : float = 0.01) -> None: 1012 """ 1013 Set the polling mode for the data transfer otherwise wait_dma() is used 1014 1015 Parameters 1016 ---------- 1017 polling : bool 1018 True to enable polling, False to disable polling 1019 timer : float | pint.Quantity 1020 the polling timer in seconds 1021 """ 1022 1023 self._polling = polling 1024 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
1027 def auto_avail_card_len(self, value : bool = None) -> bool: 1028 """ 1029 Enable or disable the automatic sending of the number of samples that the card can now use for sample data transfer again 1030 1031 Parameters 1032 ---------- 1033 value : bool = None 1034 True to enable, False to disable and None to get the current status 1035 1036 Returns 1037 ------- 1038 bool 1039 the current status 1040 """ 1041 if value is not None: 1042 self._auto_avail_card_len = value 1043 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
1102 def flush(self): 1103 """ 1104 This method is used to tell the card that a notify size of data is freed up after reading (acquisition) or written to (generation) 1105 """ 1106 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)
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.
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
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
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
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
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)
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
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
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
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)
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)
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
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
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):
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
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)
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)
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
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
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
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
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
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
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
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
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
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
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
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
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!):
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!):
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!):
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
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
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
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
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!):
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!):
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!):
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
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
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!):
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!):
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!):
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.
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.
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
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
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
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
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!):
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!):
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!):
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
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
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
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!):
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!):
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!):
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
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)
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)
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)
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)
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)
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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 buffer_size : int = 0 30 max_fill_promille : int = 1000 31 32 def __init__(self, *args, **kwargs) -> None: 33 kwargs["no_units"] = kwargs.get("no_units", True) # disable units to make the command queu faster 34 super().__init__(*args, **kwargs) 35 36 self.command_list = None 37 self.current_index = 0 38 39 self._dtm = SPCM_DDS_DTM_SINGLE 40 41 # The size of the small buffer in the DDS firmware 42 self.buffer_size = self.card.get_i(SPC_DDS_QUEUE_CMD_MAX) 43 44 self.list_size = self.default_size() 45 46 def data_transfer_mode(self, mode : int) -> None: 47 """ 48 set the data transfer mode of the DDS 49 50 Parameters 51 ---------- 52 mode : int 53 the data transfer mode 54 """ 55 56 self._dtm = mode 57 self.card.set_i(SPC_DDS_DATA_TRANSFER_MODE, mode) 58 self.list_size = self.default_size() 59 60 def default_size(self) -> int: 61 """ 62 automatically determine the size of the commands list 63 """ 64 65 if self._dtm == SPCM_DDS_DTM_SINGLE: 66 return self.buffer_size // 2 67 elif self._dtm == SPCM_DDS_DTM_DMA: 68 return KIBI(16) 69 raise SpcmException(text="Data transfer mode not supported.") 70 71 def allocate(self) -> None: 72 """ 73 allocate memory for the commands list 74 """ 75 if self.command_list is not None: 76 del self.command_list 77 elems = (ST_LIST_PARAM * (self._list_size + 1))() # +1 for the write to card command at the end 78 self.command_list = ctypes.cast(elems, ctypes.POINTER(ST_LIST_PARAM)) 79 self.current_index = 0 80 81 def load(self, data : dict, exec_mode : int = SPCM_DDS_CMD_EXEC_AT_TRG, repeat : int = 1) -> None: 82 """ 83 preload the command list with data 84 85 Parameters 86 ---------- 87 data : dict 88 the data to be preloaded 89 mode : int = SPCM_DDS_CMD_EXEC_AT_TRG 90 the mode of execution 91 repeat : int = 1 92 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. 93 94 TODO make this possible for multiple different keys 95 """ 96 97 key = list(data.keys())[0] 98 value_list = data[key] # For now only take the first key 99 size = len(value_list) 100 index = 0 101 if repeat == 0: 102 # repeat the data until the block is full 103 repeat = self.list_size // (2*size) 104 for _ in range(repeat): 105 for value in value_list: 106 # Write value 107 self.command_list[index].lReg = key 108 self.command_list[index].lType = TYPE_DOUBLE 109 self.command_list[index].dValue = value 110 index += 1 111 # Write trigger mode 112 self.command_list[index].lReg = SPC_DDS_CMD 113 self.command_list[index].lType = TYPE_INT64 114 self.command_list[index].llValue = exec_mode 115 index += 1 116 self.current_index = index 117 self.write_to_card() 118 119 def write_to_card(self) -> None: 120 """ 121 write the command list to the card 122 """ 123 124 self.command_list[self.current_index].lReg = SPC_DDS_CMD 125 self.command_list[self.current_index].lType = TYPE_INT64 126 self.command_list[self.current_index].llValue = SPCM_DDS_CMD_WRITE_TO_CARD 127 self.current_index += 1 128 129 def write(self) -> None: 130 """ 131 send the currently loaded data to the card 132 """ 133 134 if self.mode == self.WRITE_MODE.EXCEPTION_IF_FULL: 135 if self.buffer_full(): 136 raise SpcmException(text="Buffer is full") 137 elif self.mode == self.WRITE_MODE.WAIT_IF_FULL: 138 timer = 0 139 if self.data_transfer_mode == SPCM_DDS_DTM_SINGLE: 140 while self.avail_user_len() < (self.current_index): 141 self.card._print("Waiting for buffer to empty {}".format("."*(timer//100)), end="\r") 142 timer = (timer + 1) % 400 143 elif self.data_transfer_mode == SPCM_DDS_DTM_DMA: 144 while self.buffer_full(): 145 self.card._print("Waiting for buffer to empty {}".format("."*(timer//100)), end="\r") 146 timer = (timer + 1) % 400 147 self.card.set_ptr(SPC_REGISTER_LIST, self.command_list, (self.current_index) * ctypes.sizeof(ST_LIST_PARAM)) 148 149 def avail_user_len(self) -> int: 150 """ 151 get the available space for commands in the hardware queue 152 """ 153 154 if self._dtm == SPCM_DDS_DTM_SINGLE: 155 return self.buffer_size - self.card.get_i(SPC_DDS_QUEUE_CMD_COUNT) 156 elif self._dtm == SPCM_DDS_DTM_DMA: 157 # Not supported for DMA mode 158 return False 159 else: 160 raise SpcmException(text="Data transfer mode not supported.") 161 162 def buffer_full(self) -> bool: 163 """ 164 check if the command list buffer is full 165 """ 166 167 if self._dtm == SPCM_DDS_DTM_SINGLE: 168 return 1000 * (self.card.get_i(SPC_DDS_QUEUE_CMD_COUNT)/self.list_size) >= self.max_fill_promille 169 elif self._dtm == SPCM_DDS_DTM_DMA: 170 return self.card.get_i(SPC_FILLSIZEPROMILLE) >= self.max_fill_promille 171 else: 172 raise SpcmException(text="Data transfer mode not supported.") 173 174 @property 175 def list_size(self) -> int: 176 """ 177 get the size of the command list 178 """ 179 180 return self._list_size 181 182 @list_size.setter 183 def list_size(self, size : int) -> None: 184 """ 185 set the size of the command list 186 187 Parameters 188 ---------- 189 size : int 190 the size of the command list 191 """ 192 193 self._list_size = size 194 self.allocate() 195 196 def reset(self) -> None: 197 """ 198 reset the dds firmware 199 """ 200 201 # The reset shouldn't be queued! 202 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
32 def __init__(self, *args, **kwargs) -> None: 33 kwargs["no_units"] = kwargs.get("no_units", True) # disable units to make the command queu faster 34 super().__init__(*args, **kwargs) 35 36 self.command_list = None 37 self.current_index = 0 38 39 self._dtm = SPCM_DDS_DTM_SINGLE 40 41 # The size of the small buffer in the DDS firmware 42 self.buffer_size = self.card.get_i(SPC_DDS_QUEUE_CMD_MAX) 43 44 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
174 @property 175 def list_size(self) -> int: 176 """ 177 get the size of the command list 178 """ 179 180 return self._list_size
get the size of the command list
46 def data_transfer_mode(self, mode : int) -> None: 47 """ 48 set the data transfer mode of the DDS 49 50 Parameters 51 ---------- 52 mode : int 53 the data transfer mode 54 """ 55 56 self._dtm = mode 57 self.card.set_i(SPC_DDS_DATA_TRANSFER_MODE, mode) 58 self.list_size = self.default_size()
set the data transfer mode of the DDS
Parameters
- mode (int): the data transfer mode
60 def default_size(self) -> int: 61 """ 62 automatically determine the size of the commands list 63 """ 64 65 if self._dtm == SPCM_DDS_DTM_SINGLE: 66 return self.buffer_size // 2 67 elif self._dtm == SPCM_DDS_DTM_DMA: 68 return KIBI(16) 69 raise SpcmException(text="Data transfer mode not supported.")
automatically determine the size of the commands list
71 def allocate(self) -> None: 72 """ 73 allocate memory for the commands list 74 """ 75 if self.command_list is not None: 76 del self.command_list 77 elems = (ST_LIST_PARAM * (self._list_size + 1))() # +1 for the write to card command at the end 78 self.command_list = ctypes.cast(elems, ctypes.POINTER(ST_LIST_PARAM)) 79 self.current_index = 0
allocate memory for the commands list
81 def load(self, data : dict, exec_mode : int = SPCM_DDS_CMD_EXEC_AT_TRG, repeat : int = 1) -> None: 82 """ 83 preload the command list with data 84 85 Parameters 86 ---------- 87 data : dict 88 the data to be preloaded 89 mode : int = SPCM_DDS_CMD_EXEC_AT_TRG 90 the mode of execution 91 repeat : int = 1 92 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. 93 94 TODO make this possible for multiple different keys 95 """ 96 97 key = list(data.keys())[0] 98 value_list = data[key] # For now only take the first key 99 size = len(value_list) 100 index = 0 101 if repeat == 0: 102 # repeat the data until the block is full 103 repeat = self.list_size // (2*size) 104 for _ in range(repeat): 105 for value in value_list: 106 # Write value 107 self.command_list[index].lReg = key 108 self.command_list[index].lType = TYPE_DOUBLE 109 self.command_list[index].dValue = value 110 index += 1 111 # Write trigger mode 112 self.command_list[index].lReg = SPC_DDS_CMD 113 self.command_list[index].lType = TYPE_INT64 114 self.command_list[index].llValue = exec_mode 115 index += 1 116 self.current_index = index 117 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
119 def write_to_card(self) -> None: 120 """ 121 write the command list to the card 122 """ 123 124 self.command_list[self.current_index].lReg = SPC_DDS_CMD 125 self.command_list[self.current_index].lType = TYPE_INT64 126 self.command_list[self.current_index].llValue = SPCM_DDS_CMD_WRITE_TO_CARD 127 self.current_index += 1
write the command list to the card
129 def write(self) -> None: 130 """ 131 send the currently loaded data to the card 132 """ 133 134 if self.mode == self.WRITE_MODE.EXCEPTION_IF_FULL: 135 if self.buffer_full(): 136 raise SpcmException(text="Buffer is full") 137 elif self.mode == self.WRITE_MODE.WAIT_IF_FULL: 138 timer = 0 139 if self.data_transfer_mode == SPCM_DDS_DTM_SINGLE: 140 while self.avail_user_len() < (self.current_index): 141 self.card._print("Waiting for buffer to empty {}".format("."*(timer//100)), end="\r") 142 timer = (timer + 1) % 400 143 elif self.data_transfer_mode == SPCM_DDS_DTM_DMA: 144 while self.buffer_full(): 145 self.card._print("Waiting for buffer to empty {}".format("."*(timer//100)), end="\r") 146 timer = (timer + 1) % 400 147 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
149 def avail_user_len(self) -> int: 150 """ 151 get the available space for commands in the hardware queue 152 """ 153 154 if self._dtm == SPCM_DDS_DTM_SINGLE: 155 return self.buffer_size - self.card.get_i(SPC_DDS_QUEUE_CMD_COUNT) 156 elif self._dtm == SPCM_DDS_DTM_DMA: 157 # Not supported for DMA mode 158 return False 159 else: 160 raise SpcmException(text="Data transfer mode not supported.")
get the available space for commands in the hardware queue
162 def buffer_full(self) -> bool: 163 """ 164 check if the command list buffer is full 165 """ 166 167 if self._dtm == SPCM_DDS_DTM_SINGLE: 168 return 1000 * (self.card.get_i(SPC_DDS_QUEUE_CMD_COUNT)/self.list_size) >= self.max_fill_promille 169 elif self._dtm == SPCM_DDS_DTM_DMA: 170 return self.card.get_i(SPC_FILLSIZEPROMILLE) >= self.max_fill_promille 171 else: 172 raise SpcmException(text="Data transfer mode not supported.")
check if the command list buffer is full
An enumeration.
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.
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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.
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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)
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
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
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: 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, -1, 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, -1, self.num_channels), 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 if self._unpack and self._12bit_mode: 159 return self.unpack_12bit_buffer(self.buffer[current_segment:final_segment, :, :]) 160 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.
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
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
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
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: 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, -1, 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
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
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, -1, self.num_channels), 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
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.
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
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
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
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
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
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
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
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
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
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)
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
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
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 # TODO can this be merged with DataTransfer.allocate_buffer? 95 def allocate_buffer(self, num_timestamps : int) -> None: 96 """ 97 Allocate the buffer for the timestamp data transfer 98 99 Parameters 100 ---------- 101 num_timestamps : int 102 The number of timestamps to be allocated 103 """ 104 105 num_timestamps = UnitConversion.convert(num_timestamps, units.S, int) 106 self.buffer_size = num_timestamps * self.bytes_per_ts 107 108 dwMask = self._buffer_alignment - 1 109 110 sample_type = np.int64 111 item_size = sample_type(0).itemsize 112 # allocate a buffer (numpy array) for DMA transfer: a little bigger one to have room for address alignment 113 databuffer_unaligned = np.zeros(((self._buffer_alignment + self.buffer_size) // item_size, ), dtype = sample_type) # half byte count at int16 sample (// = integer division) 114 # two numpy-arrays may share the same memory: skip the begin up to the alignment boundary (ArrayVariable[SKIP_VALUE:]) 115 # Address of data-memory from numpy-array: ArrayVariable.__array_interface__['data'][0] 116 start_pos_samples = ((self._buffer_alignment - (databuffer_unaligned.__array_interface__['data'][0] & dwMask)) // item_size) 117 self.buffer = databuffer_unaligned[start_pos_samples:start_pos_samples + (self.buffer_size // item_size)] # byte address but int16 sample: therefore / 2 118 self.buffer = self.buffer.reshape((num_timestamps, 2), order='C') # array items per timestamp, because the maximum item size is 8 bytes = 64 bits 119 120 # TODO can this be merged with DataTransfer.start_buffer_transfer? 121 def start_buffer_transfer(self, *args, direction=SPCM_DIR_CARDTOPC, notify_timestamps = None, transfer_offset=0, transfer_length=None) -> None: 122 """ 123 Start the transfer of the timestamp data to the card 124 125 Parameters 126 ---------- 127 *args : list 128 list of additonal arguments that are added as flags to the start dma command 129 """ 130 131 notify_size = 0 132 if notify_timestamps is not None: 133 self._notify_timestamps = notify_timestamps 134 if self._notify_timestamps: 135 notify_size = self._notify_timestamps * self.bytes_per_ts 136 137 if transfer_offset: 138 transfer_offset_bytes = transfer_offset * self.bytes_per_ts 139 else: 140 transfer_offset_bytes = 0 141 142 if transfer_length is not None: 143 transfer_length_bytes = transfer_length * self.bytes_per_ts 144 else: 145 transfer_length_bytes = self.buffer_size 146 147 148 # we define the buffer for transfer and start the DMA transfer 149 self.card._print("Starting the Timestamp transfer and waiting until data is in board memory") 150 self._c_buffer = self.buffer.ctypes.data_as(c_void_p) 151 spcm_dwDefTransfer_i64(self.card._handle, self.buffer_type, direction, notify_size, self._c_buffer, transfer_offset_bytes, transfer_length_bytes) 152 cmd = 0 153 for arg in args: 154 cmd |= arg 155 self.card.cmd(cmd) 156 self.card._print("... timestamp data transfer started") 157 158 def avail_card_len(self, num_timestamps : int) -> None: 159 """ 160 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) 161 162 Parameters 163 ---------- 164 num_timestamps : int 165 the amount of timestamps that is available for reading 166 """ 167 card_len = num_timestamps * self.bytes_per_ts 168 self.card.set_i(SPC_TS_AVAIL_CARD_LEN, card_len) 169 170 def avail_user_pos(self) -> int: 171 """ 172 Get the current position of the pointer in the timestamp buffer (see register 'SPC_TS_AVAIL_USER_POS' in chapter `Timestamp` in the manual) 173 174 Returns 175 ------- 176 int 177 pointer position in timestamps 178 """ 179 return self.card.get_i(SPC_TS_AVAIL_USER_POS) // self.bytes_per_ts 180 181 def avail_user_len(self) -> int: 182 """ 183 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) 184 185 Returns 186 ------- 187 int 188 data length available in number of timestamps 189 """ 190 return self.card.get_i(SPC_TS_AVAIL_USER_LEN) // self.bytes_per_ts 191 192 193 # Iterator methods 194 _max_polling = 64 195 196 def to_transfer_timestamps(self, timestamps: int) -> None: 197 """ 198 This method sets the number of timestamps to transfer 199 200 Parameters 201 ---------- 202 timestamps : int 203 the number of timestamps to transfer 204 """ 205 self._to_transfer_timestamps = timestamps 206 207 def poll(self, polling_length : int = 0) -> npt.ArrayLike: 208 """ 209 This method is called when polling for timestamps 210 211 Parameters 212 ---------- 213 polling_length : int = 0 214 the number of timestamps to poll and wait for 215 216 Returns 217 ------- 218 npt.ArrayLike 219 the next data block 220 """ 221 222 while True: 223 user_pos = self.avail_user_pos() 224 user_len = self.avail_user_len() 225 if not polling_length and user_len >= 1: 226 self.avail_card_len(user_len) 227 return self.buffer[user_pos:user_pos+user_len, :] 228 elif user_len >= polling_length: 229 self.avail_card_len(polling_length) 230 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):
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
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.
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)
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
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
95 def allocate_buffer(self, num_timestamps : int) -> None: 96 """ 97 Allocate the buffer for the timestamp data transfer 98 99 Parameters 100 ---------- 101 num_timestamps : int 102 The number of timestamps to be allocated 103 """ 104 105 num_timestamps = UnitConversion.convert(num_timestamps, units.S, int) 106 self.buffer_size = num_timestamps * self.bytes_per_ts 107 108 dwMask = self._buffer_alignment - 1 109 110 sample_type = np.int64 111 item_size = sample_type(0).itemsize 112 # allocate a buffer (numpy array) for DMA transfer: a little bigger one to have room for address alignment 113 databuffer_unaligned = np.zeros(((self._buffer_alignment + self.buffer_size) // item_size, ), dtype = sample_type) # half byte count at int16 sample (// = integer division) 114 # two numpy-arrays may share the same memory: skip the begin up to the alignment boundary (ArrayVariable[SKIP_VALUE:]) 115 # Address of data-memory from numpy-array: ArrayVariable.__array_interface__['data'][0] 116 start_pos_samples = ((self._buffer_alignment - (databuffer_unaligned.__array_interface__['data'][0] & dwMask)) // item_size) 117 self.buffer = databuffer_unaligned[start_pos_samples:start_pos_samples + (self.buffer_size // item_size)] # byte address but int16 sample: therefore / 2 118 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
121 def start_buffer_transfer(self, *args, direction=SPCM_DIR_CARDTOPC, notify_timestamps = None, transfer_offset=0, transfer_length=None) -> None: 122 """ 123 Start the transfer of the timestamp data to the card 124 125 Parameters 126 ---------- 127 *args : list 128 list of additonal arguments that are added as flags to the start dma command 129 """ 130 131 notify_size = 0 132 if notify_timestamps is not None: 133 self._notify_timestamps = notify_timestamps 134 if self._notify_timestamps: 135 notify_size = self._notify_timestamps * self.bytes_per_ts 136 137 if transfer_offset: 138 transfer_offset_bytes = transfer_offset * self.bytes_per_ts 139 else: 140 transfer_offset_bytes = 0 141 142 if transfer_length is not None: 143 transfer_length_bytes = transfer_length * self.bytes_per_ts 144 else: 145 transfer_length_bytes = self.buffer_size 146 147 148 # we define the buffer for transfer and start the DMA transfer 149 self.card._print("Starting the Timestamp transfer and waiting until data is in board memory") 150 self._c_buffer = self.buffer.ctypes.data_as(c_void_p) 151 spcm_dwDefTransfer_i64(self.card._handle, self.buffer_type, direction, notify_size, self._c_buffer, transfer_offset_bytes, transfer_length_bytes) 152 cmd = 0 153 for arg in args: 154 cmd |= arg 155 self.card.cmd(cmd) 156 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
158 def avail_card_len(self, num_timestamps : int) -> None: 159 """ 160 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) 161 162 Parameters 163 ---------- 164 num_timestamps : int 165 the amount of timestamps that is available for reading 166 """ 167 card_len = num_timestamps * self.bytes_per_ts 168 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
170 def avail_user_pos(self) -> int: 171 """ 172 Get the current position of the pointer in the timestamp buffer (see register 'SPC_TS_AVAIL_USER_POS' in chapter `Timestamp` in the manual) 173 174 Returns 175 ------- 176 int 177 pointer position in timestamps 178 """ 179 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
181 def avail_user_len(self) -> int: 182 """ 183 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) 184 185 Returns 186 ------- 187 int 188 data length available in number of timestamps 189 """ 190 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
196 def to_transfer_timestamps(self, timestamps: int) -> None: 197 """ 198 This method sets the number of timestamps to transfer 199 200 Parameters 201 ---------- 202 timestamps : int 203 the number of timestamps to transfer 204 """ 205 self._to_transfer_timestamps = timestamps
This method sets the number of timestamps to transfer
Parameters
- timestamps (int): the number of timestamps to transfer
207 def poll(self, polling_length : int = 0) -> npt.ArrayLike: 208 """ 209 This method is called when polling for timestamps 210 211 Parameters 212 ---------- 213 polling_length : int = 0 214 the number of timestamps to poll and wait for 215 216 Returns 217 ------- 218 npt.ArrayLike 219 the next data block 220 """ 221 222 while True: 223 user_pos = self.avail_user_pos() 224 user_len = self.avail_user_len() 225 if not polling_length and user_len >= 1: 226 self.avail_card_len(user_len) 227 return self.buffer[user_pos:user_pos+user_len, :] 228 elif user_len >= polling_length: 229 self.avail_card_len(polling_length) 230 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
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.
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
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
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
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
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
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
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
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
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
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
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
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
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
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.
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
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
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
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.
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
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
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.
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
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
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
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.
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
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
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
102class SpcmException(Exception): 103 """a container class for handling driver level errors 104 105 Examples 106 ---------- 107 ```python 108 raise SpcmException(handle=card.handle()) 109 raise SpcmException(register=0, value=0, text="Some weird error") 110 ``` 111 112 Parameters 113 --------- 114 error : SpcmError 115 the error that induced the raising of the exception 116 117 """ 118 error = None 119 120 def __init__(self, error = None, register = None, value = None, text = None) -> None: 121 """ 122 Constructs exception object and an associated error object, either by getting 123 the last error from the card specified by the handle or using the information 124 coming from the parameters register, value and text 125 126 Parameters 127 ---------- 128 handle : drv_handle (optional) 129 a card handle to obtain the last error 130 register, value and text : int, int, str (optional) 131 parameters to define an error that is not raised by a driver error 132 """ 133 call_stack_depth = 0 134 caller_stack = inspect.stack() 135 for frame_info in caller_stack: 136 frame_module = inspect.getmodule(frame_info.frame) 137 if frame_module and frame_module.__name__.startswith('spcm.'): 138 call_stack_depth += 1 139 # Get the caller’s frame (this __init__ and the library function) 140 caller_frame = inspect.stack()[call_stack_depth].frame 141 # Create a traceback from the caller’s frame 142 self.__traceback__ = types.TracebackType( 143 tb_frame=caller_frame, 144 tb_lasti=caller_frame.f_lasti, 145 tb_lineno=caller_frame.f_lineno, 146 tb_next=None 147 ) 148 if error: self.error = error 149 if register or value or text: 150 self.error = SpcmError(register=register, value=value, text=text) 151 152 153 def __str__(self) -> str: 154 """ 155 Returns a human-readable text of the last error connected to the exception 156 157 Class Parameters 158 ---------- 159 self.error 160 161 Returns 162 ------- 163 str 164 the human-readable text as return by the error 165 """ 166 167 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
120 def __init__(self, error = None, register = None, value = None, text = None) -> None: 121 """ 122 Constructs exception object and an associated error object, either by getting 123 the last error from the card specified by the handle or using the information 124 coming from the parameters register, value and text 125 126 Parameters 127 ---------- 128 handle : drv_handle (optional) 129 a card handle to obtain the last error 130 register, value and text : int, int, str (optional) 131 parameters to define an error that is not raised by a driver error 132 """ 133 call_stack_depth = 0 134 caller_stack = inspect.stack() 135 for frame_info in caller_stack: 136 frame_module = inspect.getmodule(frame_info.frame) 137 if frame_module and frame_module.__name__.startswith('spcm.'): 138 call_stack_depth += 1 139 # Get the caller’s frame (this __init__ and the library function) 140 caller_frame = inspect.stack()[call_stack_depth].frame 141 # Create a traceback from the caller’s frame 142 self.__traceback__ = types.TracebackType( 143 tb_frame=caller_frame, 144 tb_lasti=caller_frame.f_lasti, 145 tb_lineno=caller_frame.f_lineno, 146 tb_next=None 147 ) 148 if error: self.error = error 149 if register or value or text: 150 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
169class SpcmTimeout(Exception): 170 """a container class for handling specific timeout exceptions""" 171 pass
a container class for handling specific timeout exceptions
173class SpcmDeviceNotFound(SpcmException): 174 """a container class for handling specific device not found exceptions""" 175 pass
a container class for handling specific device not found exceptions
12class SpcmError(): 13 """a container class for handling driver level errors 14 15 Examples 16 ---------- 17 ```python 18 error = SpcmError(handle=card.handle()) 19 error = SpcmError(register=0, value=0, text="Some weird error") 20 ``` 21 22 Parameters 23 --------- 24 register : int 25 the register address that triggered the error 26 27 value : int 28 the value that was written to the register address 29 30 text : str 31 the human-readable text associated with the error 32 33 """ 34 35 register : int = 0 36 value : int = 0 37 text : str = "" 38 _handle = None 39 40 def __init__(self, handle = None, register = None, value = None, text = None) -> None: 41 """ 42 Constructs an error object, either by getting the last error from the card specified by the handle 43 or using the information coming from the parameters register, value and text 44 45 Parameters 46 ---------- 47 handle : pyspcm.drv_handle (optional) 48 a card handle to obtain the last error 49 register, value and text : int, int, str (optional) 50 parameters to define an error that is not raised by a driver error 51 """ 52 if handle: 53 self._handle = handle 54 self.get_info() 55 if register: self.register = register 56 if value: self.value = value 57 if text: self.text = text 58 59 def get_info(self) -> int: 60 """ 61 Gets the last error registered by the card and puts it in the object 62 63 Class Parameters 64 ---------- 65 self.register 66 self.value 67 self.text 68 69 Returns 70 ------- 71 int 72 Error number of the spcm_dwGetErrorInfo_i32 class 73 """ 74 75 register = uint32(0) 76 value = int32(0) 77 text = create_string_buffer(ERRORTEXTLEN) 78 dwErr = spcm_dwGetErrorInfo_i32(self._handle, byref(register), byref(value), byref(text)) 79 self.register = register.value 80 self.value = value.value 81 self.text = text.value.decode('utf-8') 82 return dwErr 83 84 def __str__(self) -> str: 85 """ 86 Returns a human-readable text of the last error 87 88 Class Parameters 89 ---------- 90 self.register 91 self.value 92 self.text 93 94 Returns 95 ------- 96 str 97 the human-readable text as saved in self.text. 98 """ 99 100 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
40 def __init__(self, handle = None, register = None, value = None, text = None) -> None: 41 """ 42 Constructs an error object, either by getting the last error from the card specified by the handle 43 or using the information coming from the parameters register, value and text 44 45 Parameters 46 ---------- 47 handle : pyspcm.drv_handle (optional) 48 a card handle to obtain the last error 49 register, value and text : int, int, str (optional) 50 parameters to define an error that is not raised by a driver error 51 """ 52 if handle: 53 self._handle = handle 54 self.get_info() 55 if register: self.register = register 56 if value: self.value = value 57 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
59 def get_info(self) -> int: 60 """ 61 Gets the last error registered by the card and puts it in the object 62 63 Class Parameters 64 ---------- 65 self.register 66 self.value 67 self.text 68 69 Returns 70 ------- 71 int 72 Error number of the spcm_dwGetErrorInfo_i32 class 73 """ 74 75 register = uint32(0) 76 value = int32(0) 77 text = create_string_buffer(ERRORTEXTLEN) 78 dwErr = spcm_dwGetErrorInfo_i32(self._handle, byref(register), byref(value), byref(text)) 79 self.register = register.value 80 self.value = value.value 81 self.text = text.value.decode('utf-8') 82 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
143class SCAPPTransfer(DataTransfer, SCAPPShared): 144 def __init__(self, *args, **kwargs): 145 super().__init__(*args, **kwargs) 146 147 def _allocate_buffer(self, num_samples : int, no_reshape : bool = False, num_channels : int = 1): 148 return SCAPPShared._allocate_buffer(self, num_samples=num_samples, no_reshape=no_reshape, num_channels=num_channels) 149 150 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: 151 return SCAPPShared.start_buffer_transfer(self, *args, buffer_type=buffer_type, direction=direction, notify_samples=notify_samples, transfer_offset=transfer_offset, transfer_length=transfer_length, exception_num_samples=exception_num_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 bufferbuffer_size(int): defines the size of the current buffer shared between the PC and the cardbuffer_type(int): defines the type of data in the buffer that is used for the transfernum_channels(int): defines the number of channels that are used for the transferbytes_per_sample(int): defines the number of bytes per samplebits_per_sample(int): defines the number of bits per sampledirection(Direction = Direction.Acquisition): Direction of the data transfer.
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
150 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: 151 return SCAPPShared.start_buffer_transfer(self, *args, buffer_type=buffer_type, direction=direction, notify_samples=notify_samples, transfer_offset=transfer_offset, transfer_length=transfer_length, exception_num_samples=exception_num_samples)
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
153class SCAPPMulti(Multi, SCAPPShared): 154 def __init__(self, *args, **kwargs): 155 super().__init__(*args, **kwargs) 156 157 def _allocate_buffer(self, num_samples : int, no_reshape : bool = False, num_channels : int = 1): 158 return SCAPPShared._allocate_buffer(self, num_samples=num_samples, no_reshape=no_reshape, num_channels=num_channels) 159 160 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: 161 return SCAPPShared.start_buffer_transfer(self, *args, buffer_type=buffer_type, direction=direction, notify_samples=notify_samples, transfer_offset=transfer_offset, transfer_length=transfer_length, exception_num_samples=exception_num_samples)
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.
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
160 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: 161 return SCAPPShared.start_buffer_transfer(self, *args, buffer_type=buffer_type, direction=direction, notify_samples=notify_samples, transfer_offset=transfer_offset, transfer_length=transfer_length, exception_num_samples=exception_num_samples)
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
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() in [0x22, 0x23, 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() in [0x22, 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() in [0x22, 0x23, 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() in [0x22, 0x23, 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
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() in [0x22, 0x23, 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() in [0x22, 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
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
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
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
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() in [0x22, 0x23, 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
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() in [0x22, 0x23, 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
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