spcm
A high-level, general purpose, object-oriented Python package to control Spectrum Instrumentation GmbH devices.
spcm
can connect to digitizers, AWGs, StarHubs and Netboxes. The package contains classes for controlling specific
cards and synchronization devices (StarHub) as well as for specific functionality, such as DDS and TimeStamps.
Classes
Hardware classes
Hardware classes are the interfaces to the actual devices. These hardware classes support context management, hence the opening of specific devices is handled as a file open, using the Python with-statement.
Name | Parent | Description |
---|---|---|
Device |
(none) | general base class for connecting to Spectrum Instrumentation GmbH devices and an interface for other classes. |
Card |
Device |
a class to control the low-level API interface of Spectrum Instrumentation cards. |
Sync |
Device |
a class for controling StarHub devices. |
CardStack |
ExitStack |
a class that handles the opening and closing of a combination of different cards either with or without a StarHub that synchronizes the cards. |
Netbox |
CardStack |
a class that handles the opening and closing of a group of cards combined in a Netbox. |
Diagram
classDiagram class Device class Card class Sync class `contextlib.ExitStack` class CardStack class Netbox Device <|-- Card Device <|-- Sync `contextlib.ExitStack` <|-- CardStack CardStack <|-- Netbox
Functionality classes
Functionality classes handle specific functionality that is available to the card, as well as specific add-on options. A functionality class is provided with a hardware class object to handle functionality on that card. Some classes, in addition, also support groups of cards and can be provided with objects of type CardStack
.
Name | Parent | Description |
---|---|---|
CardFunctionality |
(none) | interface class for additional card functionality |
Channels |
(none) | class for controlling the channels of a card or a stack of cards |
Channel |
(none) | class for controlling a single channel |
Clock |
CardFunctionality |
class for setting up the clock engine of the card |
Trigger |
CardFunctionality |
class for setting up the trigger engine of the card |
MultiPurposeIOs |
CardFunctionality |
class for setting up the multi purpose i/o's of the card |
MultiPurposeIO |
(none) | class for handling a single multi purpose i/o line a list of these objects resides inside MultiPurposeIOs |
SynchronousDigitalIOs |
MultiPurposeIOs |
class for handling synchronous digital I/O on the xio lines |
DataTransfer |
CardFunctionality |
special class for handling data transfer functionality |
Multi |
DataTransfer |
special class for handling multiple recording and replay mode functionality |
Gated |
DataTransfer |
special class for handling gated recording and replay functionality |
Sequence |
DataTransfer |
special class for handling sequence mode functionality |
TimeStamp |
DataTransfer |
special class for handling time stamped data |
SCAPPTransfer |
DataTransfer |
special class for handling direct card to GPU class using the SCAPP option |
Boxcar |
Multi |
special class for handling boxcar averaging |
BlockAverage |
Multi |
special class for handling block averaging functionality |
BlockStatistics |
Multi |
special class for handling block statistics functionality |
PulseGenerators |
CardFunctionality |
class for handling the pulse generator functionality |
PulseGenerator |
(none) | class for handling a single pulse generator a list of these objects resides inside PulseGenerators |
DDS |
CardFunctionality |
class for handling DDS functionality |
DDSCore |
(none) | class for handling a DDS core, a list of these objects resides inside a DDS object |
DDSCommandList |
DDS |
class for handling streaming DDS commands in blocks |
DDSCommandQueue |
DDS |
class for handling streaming DDS commands in queues, where commands are added to the queue and automatically written to the card |
Diagram
classDiagram class CardFunctionality <<interface>> CardFunctionality class Channels class Clock class Trigger class MultiPurposeIOs class SynchronousIOs class DataTransfer class DDS class DDSCore class DDSCommandList class DDSCommandQueue class PulseGenerators class Multi class Gated class TimeStamp class Sequence class SCAPPTransfer class Boxcar class BlockAverage class BlockStatistics CardFunctionality <|-- Clock CardFunctionality <|-- Trigger CardFunctionality <|-- MultiPurposeIOs CardFunctionality <|-- DataTransfer CardFunctionality <|-- DDS CardFunctionality <|-- PulseGenerators DataTransfer <|-- Multi DataTransfer <|-- Gated DataTransfer <|-- TimeStamp DataTransfer <|-- Sequence DataTransfer <|-- SCAPPTransfer Multi <|-- Boxcar Multi <|-- BlockAverage Multi <|-- BlockStatistics Channels *-- Channel MultiPurposeIOs *-- MultiPurposeIO MultiPurposeIOs <|-- SynchronousIOs PulseGenerators *-- PulseGenerator DDS *-- DDSCore DDS <|-- DDSCommandList DDSCommandList <|-- DDSCommandQueue
Exception classes
When an error in the driver occures, the user is notified with an exception that contains an error object. Timeouts are also handled through exceptions and have their own class.
Name | Parent | Description |
---|---|---|
SpcmException |
(none) | the main class to control exceptions that are raised due to errors that are raised by the low-level driver. |
SpcmTimeout |
SpcmException |
when an timeout of the device occurs a special exception is raised of type SpcmTimeout |
SpcmDeviceNotFound |
SpcmException |
when a device is not found this is not necessarily an error, hence handling this is special |
Diagram
classDiagram class SpcmTimeout class SpcmException SpcmException <|-- SpcmTimeout SpcmException <|-- SpcmDeviceNotFound
Error classes
Errors coming from the driver API, are stored in an error object and then raised as an exception. The error object contains all the information coming from the driver.
Name | Parent | Description |
---|---|---|
SpcmError |
(none) | all the errors that are raised by the low-level driver are packed into objects of this class and handled through exceptions |
Diagram
classDiagram class SpcmError
Notes
- See the package
spcm-core
for an extensive list of all the register names and errors that are handled by the driver. - For more information, please have a look at our hardware specific user manuals.
See also
Supported devices
The following product series are supported:
- 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 58from .classes_synchronous_digital_ios import SynchronousDigitalIOs 59 60__all__ = [*__all__, 61 "Device", "Card", "Sync", "CardStack", "Netbox", "CardFunctionality", "Channels", "Channel", "Clock", "Trigger", "MultiPurposeIOs", "MultiPurposeIO", 62 "DataTransfer", "DDS", "DDSCore", "DDSCommandList", "DDSCommandQueue", "PulseGenerator", "PulseGenerators", "Multi", "Gated", "TimeStamp", "Sequence", "ABA", 63 "BlockAverage", "Boxcar", "BlockStatistics", "SpcmException", "SpcmTimeout", "SpcmDeviceNotFound", "SpcmError", "SCAPPTransfer", "SynchronousDigitalIOs" 64] 65 66# Versioning support using versioneer 67from . import _version 68__version__ = _version.get_versions()['version'] 69 70# Writing spcm package version to log file 71try: 72 driver_version = int64(0) 73 spcm_dwGetParam_i64(None, SPC_GETDRVVERSION, byref(driver_version)) 74 version_hex = driver_version.value 75 major = (version_hex & 0xFF000000) >> 24 76 minor = (version_hex & 0x00FF0000) >> 16 77 build = version_hex & 0x0000FFFF 78 # Available starting from build 21797 79 if build < 21797: 80 version_str = "v{}.{}.{}".format(major, minor, build) 81 raise OSError(f"Driver version build {version_str} does not support writing spcm version to log") 82 from importlib.metadata import version 83 version_tag = version('spcm') 84 version_str = bytes("Python package spcm v{}".format(version_tag), "utf-8") 85 version_ptr = create_string_buffer(version_str) 86 dwErr = spcm_dwSetParam_ptr(None, SPC_WRITE_TO_LOG, version_ptr, len(version_str)) 87except OSError as e: 88 print(e)
21class Device(): 22 """ 23 a class to control the low-level API interface of Spectrum Instrumentation devices 24 25 For more information about what setups are available, please have a look at the user manual 26 for your specific device. 27 28 Parameters 29 ---------- 30 device_identifier 31 the identifying string that defines the used device 32 33 Raises 34 ---------- 35 SpcmException 36 SpcmTimeout 37 """ 38 39 # public 40 device_identifier : str = "" 41 42 # private 43 _kwargs : Dict[str, Union[int, float, str]] = {} 44 _last_error = None 45 _handle = None 46 """the handle object used for the card connection""" 47 48 _str_len = 256 49 _reraise : bool = False 50 _throw_error : bool = True 51 _verbose : bool = False 52 _closed : bool = True 53 """the indicator that indicated whether a connection is opened or closed is set to open (False)""" 54 55 56 def __init__(self, device_identifier : str = "", handle = False, **kwargs) -> None: 57 """Puts the device_identifier in the class parameter self.device_parameter 58 59 Parameters 60 ---------- 61 device_identifier : str = "" 62 an identifier string to connect to a specific device, for example: 63 64 * Local PCIe device '/dev/spcm0' 65 * Remote 'TCPIP::192.168.1.10::inst0::INSTR' 66 handle = False 67 directly supply the object with an existing handle 68 """ 69 self.device_identifier = device_identifier 70 self._handle = handle 71 self._kwargs = kwargs 72 self._throw_error = kwargs.get("throw_error", True) 73 self._verbose = kwargs.get("verbose", False) 74 75 def __del__(self) -> None: 76 """Destructor that closes the connection associated with the handle""" 77 self.close() 78 79 def __enter__(self) -> object: 80 """ 81 Constructs a handle using the parameter `device_identifier`, when using the with statement 82 83 Returns 84 ------- 85 object 86 The active card handle 87 88 Raises 89 ------ 90 SpcmException 91 """ 92 return self.open() 93 94 def open(self, device_identifier : str = None) -> object: 95 """ 96 Opens a connection to the card and creates a handle, when no with statement is used 97 98 Parameters 99 ---------- 100 device_identifier : str 101 The card identifier string (e.g. '/dev/spcm0' for a local device 102 NOTE: this is to keep the API consistent with a previous version. The original open() 103 method is now in _open() 104 105 Returns 106 ------- 107 object 108 This Card object 109 """ 110 111 if device_identifier: # this is to keep the API consistent 112 return self.open_handle(device_identifier) 113 # This used to be in enter. It is now split up to allow for the open method 114 # to be used when no with statement is used 115 if self.device_identifier and not self._handle: 116 self.open_handle(self.device_identifier) 117 if not self._handle and self._throw_error: 118 error = SpcmError(text="{} not found...".format(self.device_identifier)) 119 raise SpcmDeviceNotFound(error) 120 # if self._handle: 121 # self._closed = False 122 return self 123 124 def __exit__(self, exception : SpcmException = None, error_value : str = None, trace : types.TracebackType = None) -> None: 125 """ 126 Handles the exiting of the with statement, when either no code is left or an exception is thrown before 127 128 Parameters 129 ---------- 130 exception : SpcmException 131 Only this parameter is used and printed 132 error_value : str 133 trace : types.TracebackType 134 135 Raises 136 ------ 137 SpcmException 138 """ 139 if self._verbose and exception: 140 self._print("Error type: {}".format(exception)) 141 self._print("Error value: {}".format(error_value)) 142 self._print("Traceback:") 143 traceback.print_tb(trace) 144 elif exception: 145 self._print("Error: {}".format(error_value)) 146 # self.stop(M2CMD_DATA_STOPDMA) # stop the card and the DMA transfer 147 self.close() 148 if exception and self._reraise: 149 raise exception 150 151 def close(self) -> None: 152 """ 153 Closes the connection to the card using a handle 154 """ 155 156 if not self._closed: 157 self.stop(M2CMD_DATA_STOPDMA) # stop the card and the DMA transfer 158 self.close_handle() 159 160 def handle(self) -> object: 161 """ 162 Returns the handle used by the object to connect to the active card 163 164 Class Parameters 165 ---------- 166 self._handle 167 168 Returns 169 ------- 170 drv_handle 171 The active card handle 172 """ 173 174 return self._handle 175 176 # Check if a card was found 177 def __bool__(self) -> bool: 178 """ 179 Check for a connection to the active card 180 181 Class Parameters 182 ---------- 183 self._handle 184 185 Returns 186 ------- 187 bool 188 True for an active connection and false otherwise 189 190 Examples 191 ----------- 192 >>> card = spcm.Card('/dev/spcm0') 193 >>> print(bool(card)) 194 <<< True # if a card was found at '/dev/spcm0' 195 """ 196 197 return bool(self._handle) 198 199 # High-level parameter functions, that use the low-level get and set function 200 def drv_type(self) -> int: 201 """ 202 Get the driver type of the currently used driver (see register `SPC_GETDRVTYPE` in the manual) 203 204 Returns 205 ------- 206 int 207 The driver type of the currently used driver 208 """ 209 210 return self.get_i(SPC_GETDRVTYPE) 211 212 def drv_version(self) -> dict: 213 """ 214 Get the version of the currently used driver. (see register `SPC_GETDRVVERSION` in the manual) 215 216 Returns 217 ------- 218 dict 219 version of the currently used driver 220 * "major" - the major version number, 221 * "minor" - the minor version number, 222 * "build" - the actual build 223 """ 224 version_hex = self.get_i(SPC_GETDRVVERSION) 225 major = (version_hex & 0xFF000000) >> 24 226 minor = (version_hex & 0x00FF0000) >> 16 227 build = version_hex & 0x0000FFFF 228 version_dict = {"major": major, "minor": minor, "build": build} 229 return version_dict 230 231 def kernel_version(self) -> dict: 232 """ 233 Get the version of the currently used kernel. (see register `SPC_GETKERNELVERSION` in the manual) 234 235 Returns 236 ------- 237 dict 238 version of the currently used driver 239 * "major" - the major version number, 240 * "minor" - the minor version number, 241 * "build" - the actual build 242 """ 243 version_hex = self.get_i(SPC_GETKERNELVERSION) 244 major = (version_hex & 0xFF000000) >> 24 245 minor = (version_hex & 0x00FF0000) >> 16 246 build = version_hex & 0x0000FFFF 247 version_dict = {"major": major, "minor": minor, "build": build} 248 return version_dict 249 250 def custom_modifications(self) -> dict: 251 """ 252 Get the custom modifications of the currently used device. (see register `SPCM_CUSTOMMOD` in the manual) 253 254 Returns 255 ------- 256 dict 257 The custom modifications of the currently used device 258 * "starhub" - custom modifications to the starhub, 259 * "module" - custom modification of the front-end module(s) 260 * "base" - custom modification of the base card 261 """ 262 263 custom_mode = self.get_i(SPCM_CUSTOMMOD) 264 starhub = (custom_mode & SPCM_CUSTOMMOD_STARHUB_MASK) >> 16 265 module = (custom_mode & SPCM_CUSTOMMOD_MODULE_MASK) >> 8 266 base = custom_mode & SPCM_CUSTOMMOD_BASE_MASK 267 custom_dict = {"starhub": starhub, "module": module, "base": base} 268 return custom_dict 269 270 def log_level(self, log_level : int = None) -> int: 271 """ 272 Set the logging level of the driver 273 274 Parameters 275 ---------- 276 log_level : int 277 The logging level that is set for the driver 278 279 Returns 280 ------- 281 int 282 The logging level of the driver 283 """ 284 285 if log_level is not None: 286 self.set_i(SPC_LOGDLLCALLS, log_level) 287 return self.get_i(SPC_LOGDLLCALLS) 288 289 def cmd(self, *args) -> None: 290 """ 291 Execute spcm commands (see register `SPC_M2CMD` in the manual) 292 293 Parameters 294 ---------- 295 *args : int 296 The different command flags to be executed. 297 """ 298 299 cmd = 0 300 for arg in args: 301 cmd |= arg 302 self.set_i(SPC_M2CMD, cmd) 303 304 #@Decorators.unitize(units.ms, "timeout", int) 305 def timeout(self, timeout : int = None, return_unit = None) -> int: 306 """ 307 Sets the timeout in ms (see register `SPC_TIMEOUT` in the manual) 308 309 Parameters 310 ---------- 311 timeout : int 312 The timeout in ms 313 314 Returns 315 ------- 316 int 317 returns the current timeout in ms 318 """ 319 320 if timeout is not None: 321 timeout = UnitConversion.convert(timeout, units.ms, int) 322 self.set_i(SPC_TIMEOUT, timeout) 323 return_value = self.get_i(SPC_TIMEOUT) 324 return_value = UnitConversion.to_unit(return_value * units.ms, return_unit) 325 return return_value 326 327 def start(self, *args) -> None: 328 """ 329 Starts the connected card and enables triggering on the card (see command `M2CMD_CARD_START` in the manual) 330 331 Parameters 332 ---------- 333 *args : int 334 flags that are send together with the start command 335 """ 336 337 self.cmd(M2CMD_CARD_START, *args) 338 339 def stop(self, *args : int) -> None: 340 """ 341 Stops the connected card (see command `M2CMD_CARD_STOP` in the manual) 342 343 Parameters 344 ---------- 345 *args : int 346 flags that are send together with the stop command (e.g. M2CMD_DATA_STOPDMA) 347 """ 348 349 self.cmd(M2CMD_CARD_STOP, *args) 350 351 def reset(self) -> None: 352 """ 353 Resets the connected device (see command `M2CMD_CARD_RESET` in the manual) 354 """ 355 356 self.cmd(M2CMD_CARD_RESET) 357 358 def write_setup(self, *args) -> None: 359 """ 360 Writes of the configuration registers previously changed to the device (see command `M2CMD_CARD_WRITESETUP` in the manual) 361 362 Parameters 363 ---------- 364 *args : int 365 flags that are set with the write command 366 """ 367 368 self.cmd(M2CMD_CARD_WRITESETUP, *args) 369 370 def register_list(self, register_list : List[dict[str, Union[int, float]]]) -> None: 371 """ 372 Writes a list with dictionaries, where each dictionary corresponds to a command (see the user manual of your device for all the available registers) 373 374 Parameters 375 ---------- 376 register_list : List[dict[str, Union[int, float]]] 377 The list of commands that needs to written to the specific registers of the card. 378 """ 379 380 c_astParams = (ST_LIST_PARAM * 1024)() 381 astParams = ctypes.cast(c_astParams, ctypes.POINTER(ST_LIST_PARAM)) 382 for i, register in enumerate(register_list): 383 astParams[i].lReg = register["lReg"] 384 astParams[i].lType = register["lType"] 385 if register["lType"] == TYPE_INT64: 386 astParams[i].Value.llValue = register["llValue"] 387 elif register["lType"] == TYPE_DOUBLE: 388 astParams[i].Value.dValue = register["dValue"] 389 self.set_ptr(SPC_REGISTER_LIST, astParams, len(register_list) * ctypes.sizeof(ST_LIST_PARAM)) 390 391 # Low-level get and set functions 392 def get_i(self, register : int) -> int: 393 """ 394 Get the integer value of a specific register of the card (see the user manual of your device for all the available registers) 395 396 Parameters 397 ---------- 398 register : int 399 The specific register that will be read from. 400 401 Returns 402 ------- 403 int 404 The value as stored in the specific register 405 """ 406 407 self._check_closed() 408 return_value = int64(0) 409 dwErr = spcm_dwGetParam_i64(self._handle, register, byref(return_value)) 410 self._check_error(dwErr) 411 return return_value.value 412 get = get_i 413 """Alias of get_i""" 414 415 def get_d(self, register : int) -> float: 416 """ 417 Get the float value of a specific register of the card (see the user manual of your device for all the available registers) 418 419 Parameters 420 ---------- 421 register : int 422 The specific register that will be read from. 423 424 Returns 425 ------- 426 float 427 The value as stored in the specific register 428 """ 429 430 self._check_closed() 431 return_value = c_double(0) 432 self._check_error(spcm_dwGetParam_d64(self._handle, register, byref(return_value))) 433 return return_value.value 434 435 def get_str(self, register : int) -> str: 436 """ 437 Get the string value of a specific register of the card (see the user manual of your device for all the available registers) 438 439 Parameters 440 ---------- 441 register : int 442 The specific register that will be read from. 443 444 Returns 445 ------- 446 str 447 The value as stored in the specific register 448 """ 449 450 self._check_closed() 451 return_value = create_string_buffer(self._str_len) 452 self._check_error(spcm_dwGetParam_ptr(self._handle, register, byref(return_value), self._str_len)) 453 return return_value.value.decode('utf-8') 454 455 def set_i(self, register : int, value : int) -> None: 456 """ 457 Write the value of a specific register to the card (see the user manual of your device for all the available registers) 458 459 Parameters 460 ---------- 461 register : int 462 The specific register that will be written. 463 value : int 464 The value that is written to the card. 465 """ 466 467 self._check_closed() 468 self._check_error(spcm_dwSetParam_i64(self._handle, register, value)) 469 470 def set_d(self, register : int, value : float) -> None: 471 """ 472 Write the value of a specific register to the card (see the user manual of your device for all the available registers) 473 474 Parameters 475 ---------- 476 register : int 477 The specific register that will be written. 478 value : float 479 The value that is written to the card. 480 """ 481 482 self._check_closed() 483 self._check_error(spcm_dwSetParam_d64(self._handle, register, value)) 484 485 def set_ptr(self, register : int, reference : c_void_p, size : int) -> None: 486 """ 487 Use a memory segment to write to a specific register of the card (see the user manual of your device for all the available registers) 488 489 Parameters 490 ---------- 491 register : int 492 The specific register that will be read from. 493 reference : c_void_p 494 pointer to the memory segment 495 size : int 496 size of the memory segment 497 498 Returns 499 ------- 500 int 501 The value as stored in the specific register 502 """ 503 504 self._check_closed() 505 self._check_error(spcm_dwSetParam_ptr(self._handle, register, reference, size)) 506 507 # Error handling and exception raising 508 def _check_error(self, dwErr : int): 509 """ 510 Create an SpcmError object and check for the last error (see the appendix in the user manual of your device for all the possible error codes) 511 512 Parameters 513 ---------- 514 dwErr : int 515 The error value as returned from a direct driver call 516 517 Raises 518 ------ 519 SpcmException 520 SpcmTimeout 521 """ 522 523 # pass 524 if dwErr not in [ERR_OK, ERR_TIMEOUT] and self._throw_error: 525 self.get_error_info() 526 raise SpcmException(self._last_error) 527 elif dwErr == ERR_TIMEOUT: 528 raise SpcmTimeout("A card timeout occured") 529 530 def get_error_info(self) -> SpcmError: 531 """ 532 Create an SpcmError object and store it in an object parameter 533 534 Returns 535 ---------- 536 SpcmError 537 the Error object containing the last error 538 """ 539 540 self._last_error = SpcmError(self._handle) 541 return self._last_error 542 543 def _check_closed(self) -> None: 544 """ 545 Check if a connection to the card exists and if not throw an error 546 547 Raises 548 ------ 549 SpcmException 550 """ 551 if self._closed: 552 error_text = "The connection to the card has been closed. Please reopen the connection before sending commands." 553 if self._throw_error: 554 raise SpcmException(text=error_text) 555 else: 556 self._print(error_text) 557 558 def _print(self, text : str, verbose : bool = False, **kwargs) -> None: 559 """ 560 Print information 561 562 Parameters 563 ---------- 564 text : str 565 The text that is printed 566 verbose : bool 567 A boolean that indicates if the text should forced to be printed 568 **kwargs 569 Additional parameters that are passed to the print function 570 571 """ 572 573 if self._verbose or verbose: 574 print(text, **kwargs) 575 576 def open_handle(self, device_identifier : str) -> None: 577 """ 578 Open a connection to the card and create a handle (see the user manual of your specific device on how to find out the device_identifier string) 579 580 Parameters 581 ---------- 582 device_identifier : str 583 The card identifier string (e.g. '/dev/spcm0' for a local device or 'TCPIP::192.168.1.10::inst0::INSTR' for a remote device) 584 """ 585 586 self._handle = spcm_hOpen(create_string_buffer(bytes(device_identifier, 'utf-8'))) 587 self._closed = False 588 589 def close_handle(self) -> None: 590 """ 591 Close a connection to the card using the handle 592 """ 593 594 spcm_vClose(self._handle) 595 self._handle = None 596 self._closed = True
a class to control the low-level API interface of Spectrum Instrumentation devices
For more information about what setups are available, please have a look at the user manual for your specific device.
Parameters
- device_identifier: the identifying string that defines the used device
Raises
- SpcmException
- SpcmTimeout
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.
305 def timeout(self, timeout : int = None, return_unit = None) -> int: 306 """ 307 Sets the timeout in ms (see register `SPC_TIMEOUT` in the manual) 308 309 Parameters 310 ---------- 311 timeout : int 312 The timeout in ms 313 314 Returns 315 ------- 316 int 317 returns the current timeout in ms 318 """ 319 320 if timeout is not None: 321 timeout = UnitConversion.convert(timeout, units.ms, int) 322 self.set_i(SPC_TIMEOUT, timeout) 323 return_value = self.get_i(SPC_TIMEOUT) 324 return_value = UnitConversion.to_unit(return_value * units.ms, return_unit) 325 return return_value
Sets the timeout in ms (see register SPC_TIMEOUT
in the manual)
Parameters
- timeout (int): The timeout in ms
Returns
- int: returns the current timeout in ms
327 def start(self, *args) -> None: 328 """ 329 Starts the connected card and enables triggering on the card (see command `M2CMD_CARD_START` in the manual) 330 331 Parameters 332 ---------- 333 *args : int 334 flags that are send together with the start command 335 """ 336 337 self.cmd(M2CMD_CARD_START, *args)
Starts the connected card and enables triggering on the card (see command M2CMD_CARD_START
in the manual)
Parameters
- *args (int): flags that are send together with the start command
339 def stop(self, *args : int) -> None: 340 """ 341 Stops the connected card (see command `M2CMD_CARD_STOP` in the manual) 342 343 Parameters 344 ---------- 345 *args : int 346 flags that are send together with the stop command (e.g. M2CMD_DATA_STOPDMA) 347 """ 348 349 self.cmd(M2CMD_CARD_STOP, *args)
Stops the connected card (see command M2CMD_CARD_STOP
in the manual)
Parameters
- *args (int): flags that are send together with the stop command (e.g. M2CMD_DATA_STOPDMA)
351 def reset(self) -> None: 352 """ 353 Resets the connected device (see command `M2CMD_CARD_RESET` in the manual) 354 """ 355 356 self.cmd(M2CMD_CARD_RESET)
Resets the connected device (see command M2CMD_CARD_RESET
in the manual)
358 def write_setup(self, *args) -> None: 359 """ 360 Writes of the configuration registers previously changed to the device (see command `M2CMD_CARD_WRITESETUP` in the manual) 361 362 Parameters 363 ---------- 364 *args : int 365 flags that are set with the write command 366 """ 367 368 self.cmd(M2CMD_CARD_WRITESETUP, *args)
Writes of the configuration registers previously changed to the device (see command M2CMD_CARD_WRITESETUP
in the manual)
Parameters
- *args (int): flags that are set with the write command
370 def register_list(self, register_list : List[dict[str, Union[int, float]]]) -> None: 371 """ 372 Writes a list with dictionaries, where each dictionary corresponds to a command (see the user manual of your device for all the available registers) 373 374 Parameters 375 ---------- 376 register_list : List[dict[str, Union[int, float]]] 377 The list of commands that needs to written to the specific registers of the card. 378 """ 379 380 c_astParams = (ST_LIST_PARAM * 1024)() 381 astParams = ctypes.cast(c_astParams, ctypes.POINTER(ST_LIST_PARAM)) 382 for i, register in enumerate(register_list): 383 astParams[i].lReg = register["lReg"] 384 astParams[i].lType = register["lType"] 385 if register["lType"] == TYPE_INT64: 386 astParams[i].Value.llValue = register["llValue"] 387 elif register["lType"] == TYPE_DOUBLE: 388 astParams[i].Value.dValue = register["dValue"] 389 self.set_ptr(SPC_REGISTER_LIST, astParams, len(register_list) * ctypes.sizeof(ST_LIST_PARAM))
Writes a list with dictionaries, where each dictionary corresponds to a command (see the user manual of your device for all the available registers)
Parameters
- register_list (List[dict[str, Union[int, float]]]): The list of commands that needs to written to the specific registers of the card.
392 def get_i(self, register : int) -> int: 393 """ 394 Get the integer value of a specific register of the card (see the user manual of your device for all the available registers) 395 396 Parameters 397 ---------- 398 register : int 399 The specific register that will be read from. 400 401 Returns 402 ------- 403 int 404 The value as stored in the specific register 405 """ 406 407 self._check_closed() 408 return_value = int64(0) 409 dwErr = spcm_dwGetParam_i64(self._handle, register, byref(return_value)) 410 self._check_error(dwErr) 411 return return_value.value
Get the integer value of a specific register of the card (see the user manual of your device for all the available registers)
Parameters
- register (int): The specific register that will be read from.
Returns
- int: The value as stored in the specific register
392 def get_i(self, register : int) -> int: 393 """ 394 Get the integer value of a specific register of the card (see the user manual of your device for all the available registers) 395 396 Parameters 397 ---------- 398 register : int 399 The specific register that will be read from. 400 401 Returns 402 ------- 403 int 404 The value as stored in the specific register 405 """ 406 407 self._check_closed() 408 return_value = int64(0) 409 dwErr = spcm_dwGetParam_i64(self._handle, register, byref(return_value)) 410 self._check_error(dwErr) 411 return return_value.value
Alias of get_i
415 def get_d(self, register : int) -> float: 416 """ 417 Get the float value of a specific register of the card (see the user manual of your device for all the available registers) 418 419 Parameters 420 ---------- 421 register : int 422 The specific register that will be read from. 423 424 Returns 425 ------- 426 float 427 The value as stored in the specific register 428 """ 429 430 self._check_closed() 431 return_value = c_double(0) 432 self._check_error(spcm_dwGetParam_d64(self._handle, register, byref(return_value))) 433 return return_value.value
Get the float value of a specific register of the card (see the user manual of your device for all the available registers)
Parameters
- register (int): The specific register that will be read from.
Returns
- float: The value as stored in the specific register
435 def get_str(self, register : int) -> str: 436 """ 437 Get the string value of a specific register of the card (see the user manual of your device for all the available registers) 438 439 Parameters 440 ---------- 441 register : int 442 The specific register that will be read from. 443 444 Returns 445 ------- 446 str 447 The value as stored in the specific register 448 """ 449 450 self._check_closed() 451 return_value = create_string_buffer(self._str_len) 452 self._check_error(spcm_dwGetParam_ptr(self._handle, register, byref(return_value), self._str_len)) 453 return return_value.value.decode('utf-8')
Get the string value of a specific register of the card (see the user manual of your device for all the available registers)
Parameters
- register (int): The specific register that will be read from.
Returns
- str: The value as stored in the specific register
455 def set_i(self, register : int, value : int) -> None: 456 """ 457 Write the value of a specific register to the card (see the user manual of your device for all the available registers) 458 459 Parameters 460 ---------- 461 register : int 462 The specific register that will be written. 463 value : int 464 The value that is written to the card. 465 """ 466 467 self._check_closed() 468 self._check_error(spcm_dwSetParam_i64(self._handle, register, value))
Write the value of a specific register to the card (see the user manual of your device for all the available registers)
Parameters
- register (int): The specific register that will be written.
- value (int): The value that is written to the card.
470 def set_d(self, register : int, value : float) -> None: 471 """ 472 Write the value of a specific register to the card (see the user manual of your device for all the available registers) 473 474 Parameters 475 ---------- 476 register : int 477 The specific register that will be written. 478 value : float 479 The value that is written to the card. 480 """ 481 482 self._check_closed() 483 self._check_error(spcm_dwSetParam_d64(self._handle, register, value))
Write the value of a specific register to the card (see the user manual of your device for all the available registers)
Parameters
- register (int): The specific register that will be written.
- value (float): The value that is written to the card.
485 def set_ptr(self, register : int, reference : c_void_p, size : int) -> None: 486 """ 487 Use a memory segment to write to a specific register of the card (see the user manual of your device for all the available registers) 488 489 Parameters 490 ---------- 491 register : int 492 The specific register that will be read from. 493 reference : c_void_p 494 pointer to the memory segment 495 size : int 496 size of the memory segment 497 498 Returns 499 ------- 500 int 501 The value as stored in the specific register 502 """ 503 504 self._check_closed() 505 self._check_error(spcm_dwSetParam_ptr(self._handle, register, reference, size))
Use a memory segment to write to a specific register of the card (see the user manual of your device for all the available registers)
Parameters
- register (int): The specific register that will be read from.
- reference (c_void_p): pointer to the memory segment
- size (int): size of the memory segment
Returns
- int: The value as stored in the specific register
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 31 def __enter__(self) -> 'Card': 32 """ 33 Context manager entry function 34 35 Returns 36 ------- 37 Card 38 The card object 39 40 Raises 41 ------ 42 SpcmException 43 """ 44 return super().__enter__() 45 46 def open(self, device_identifier : str = None) -> 'Card': 47 """ 48 Open a connection to the card 49 50 Parameters 51 ---------- 52 device_identifier : str = "" 53 The device identifier of the card that needs to be opened 54 55 Returns 56 ------- 57 Card 58 The card object 59 60 Raises 61 ------ 62 SpcmException 63 """ 64 65 if device_identifier is not None: 66 return super().open(device_identifier=device_identifier) 67 68 super().open() 69 70 # keyword arguments 71 card_type = self._kwargs.get("card_type", 0) 72 serial_number = self._kwargs.get("serial_number", 0) 73 74 if self.device_identifier == "": 75 # No device identifier was given, so we need to find the first card 76 self._handle = self.find(card_type=card_type, serial_number=serial_number) 77 if not self._handle: 78 if card_type: 79 raise SpcmException(text="No card found of right type") 80 elif serial_number: 81 raise SpcmException(text="No card found with serial number: {}".format(serial_number)) 82 else: 83 self._closed = False 84 elif self._handle: 85 if card_type != 0 and self.function_type() != card_type: 86 raise SpcmException(text="The card with the given device identifier is not the correct type") 87 elif serial_number != 0 and self.sn() != serial_number: 88 raise SpcmException(text="The card with the given device identifier does not have the correct serial number") 89 90 # Check python, driver and kernel version 91 if self._verbose: 92 print("Python version: {} on {}".format (platform.python_version(), platform.system())) 93 print("Driver version: {major}.{minor}.{build}".format(**self.drv_version())) 94 print("Kernel version: {major}.{minor}.{build}".format(**self.kernel_version())) 95 if self._handle: 96 print("Found '{}': {} sn {:05d}".format(self.device_identifier, self.product_name(), self.sn())) 97 98 # Get the function type of the card 99 self._function_type = self.get_i(SPC_FNCTYPE) 100 self._card_type = self.get_i(SPC_PCITYP) 101 self._features = self.get_i(SPC_PCIFEATURES) 102 self._ext_features = self.get_i(SPC_PCIEXTFEATURES) 103 self._max_sample_value = self.get_i(SPC_MIINST_MAXADCVALUE) 104 105 return self 106 107 def __str__(self) -> str: 108 """ 109 String representation of the card 110 111 Returns 112 ------- 113 str 114 String representation of the card 115 """ 116 return "Card: {} sn {:05d}".format(self.product_name(), self.sn()) 117 __repr__ = __str__ 118 119 def find(self, card_type : int = 0, serial_number : int = 0) -> Union[bool, int]: 120 """ 121 Find first card that is connected to the computer, with either the given card type or serial number 122 123 Parameters 124 ---------- 125 card_type : int = 0 126 The function type of the card that needs to be found 127 serial_number : int = 0 128 The serial number of the card that needs to be found 129 130 Returns 131 ------- 132 Union[bool, int] 133 False if no card is found, otherwise the handle of the card 134 135 """ 136 for nr in range(self._max_cards): 137 device_identifier = self._std_device_identifier.format(nr) 138 handle = spcm_hOpen(ctypes.create_string_buffer(bytes(device_identifier, 'utf-8'))) 139 if handle: 140 self.device_identifier = device_identifier 141 return_value = ctypes.c_int64() 142 spcm_dwGetParam_i64(handle, SPC_FNCTYPE, ctypes.byref(return_value)) 143 function_type = return_value.value 144 spcm_dwGetParam_i64(handle, SPC_PCISERIALNO, ctypes.byref(return_value)) 145 sn = return_value.value 146 if card_type != 0 and (card_type & function_type) == function_type: 147 return handle 148 elif sn != 0 and sn == serial_number: 149 return handle 150 elif serial_number == 0 and card_type == 0: 151 return handle 152 spcm_vClose(handle) 153 return False 154 155 156 # High-level parameter functions, that use the low-level get and set function 157 def status(self) -> int: 158 """ 159 Get the status of the card (see register `SPC_M2STATUS` in the manual) 160 161 Returns 162 ------- 163 int 164 The status of the card 165 """ 166 167 return self.get_i(SPC_M2STATUS) 168 169 def card_type(self) -> int: 170 """ 171 Get the card type of the card (see register `SPC_PCITYP` in the manual) 172 173 Returns 174 ------- 175 int 176 The card type of the card 177 """ 178 179 return self._card_type 180 181 def series(self) -> int: 182 """ 183 Get the series of the card (see register `SPC_PCITYP` and `TYP_SERIESMASK` in the manual) 184 185 Returns 186 ------- 187 int 188 The series of the card 189 """ 190 191 return self.card_type() & TYP_SERIESMASK 192 193 def family(self) -> int: 194 """ 195 Get the family of the card (see register `SPC_PCITYP` and `TYP_FAMILYMASK` in the manual) 196 197 Returns 198 ------- 199 int 200 The family of the card 201 """ 202 203 return (self.card_type() & TYP_FAMILYMASK) >> 8 204 205 def function_type(self) -> int: 206 """ 207 Gives information about what type of card it is. (see register `SPC_FNCTYPE` in the manual) 208 209 Returns 210 ------- 211 int 212 The function type of the card 213 214 * SPCM_TYPE_AI = 1h - Analog input card (analog acquisition; the M2i.4028 and M2i.4038 also return this value) 215 * SPCM_TYPE_AO = 2h - Analog output card (arbitrary waveform generators) 216 * SPCM_TYPE_DI = 4h - Digital input card (logic analyzer card) 217 * SPCM_TYPE_DO = 8h - Digital output card (pattern generators) 218 * SPCM_TYPE_DIO = 10h - Digital I/O (input/output) card, where the direction is software selectable. 219 """ 220 221 return self._function_type 222 223 def features(self) -> int: 224 """ 225 Get the features of the card (see register `SPC_PCIFEATURES` in the manual) 226 227 Returns 228 ------- 229 int 230 The features of the card 231 """ 232 233 return self._features 234 235 def ext_features(self) -> int: 236 """ 237 Get the extended features of the card (see register `SPC_PCIEXTFEATURES` in the manual) 238 239 Returns 240 ------- 241 int 242 The extended features of the card 243 """ 244 245 return self._ext_features 246 247 def starhub_card(self) -> bool: 248 """ 249 Check if the card is a starhub card (see register `SPC_PCIFEATURES` in the manual) 250 251 Returns 252 ------- 253 bool 254 True if the card is the card that carriers a starhub, False otherwise 255 """ 256 257 return bool(self._features & SPCM_FEAT_STARHUBXX_MASK) 258 259 def num_modules(self) -> int: 260 """ 261 Get the number of modules of the card (see register `SPC_MIINST_MODULES` in the manual) 262 263 Returns 264 ------- 265 int 266 The number of modules of the card 267 """ 268 269 return self.get_i(SPC_MIINST_MODULES) 270 271 def channels_per_module(self) -> int: 272 """ 273 Get the number of channels per module of the card (see register `SPC_MIINST_CHPERMODULE` in the manual) 274 275 Returns 276 ------- 277 int 278 The number of channels per module of the card 279 """ 280 281 return self.get_i(SPC_MIINST_CHPERMODULE) 282 283 def num_channels(self) -> int: 284 """ 285 Get the number of channels of the card (= SPC_MIINST_MODULES * SPC_MIINST_CHPERMODULE) 286 287 Returns 288 ------- 289 int 290 The number of channels of the card 291 """ 292 293 return self.num_modules() * self.channels_per_module() 294 295 def card_mode(self, card_mode : int = None) -> int: 296 """ 297 Set the card mode of the connected card (see register `SPC_CARDMODE` in the manual) 298 299 Parameters 300 ---------- 301 card_mode : int 302 the mode that the card needs to operate in 303 304 Returns 305 ------- 306 int 307 the mode that the card operates in 308 """ 309 310 if card_mode is not None: 311 self.set_i(SPC_CARDMODE, card_mode) 312 return self.get_i(SPC_CARDMODE) 313 314 def product_name(self) -> str: 315 """ 316 Get the product name of the card (see register `SPC_PCITYP` in the manual) 317 318 Returns 319 ------- 320 str 321 The product name of the connected card (e.g. M4i.6631-x8) 322 """ 323 324 return self.get_str(SPC_PCITYP) 325 326 def sn(self) -> int: 327 """ 328 Get the serial number of a product (see register `SPC_PCISERIALNO` in the manual) 329 330 Returns 331 ------- 332 int 333 The serial number of the connected card (e.g. 12345) 334 """ 335 336 return self.get_i(SPC_PCISERIALNO) 337 338 def active_channels(self) -> int: 339 """ 340 Get the number of channels of the card (see register `SPC_CHCOUNT` in the manual) 341 342 Returns 343 ------- 344 int 345 The number of channels of the card 346 """ 347 348 return self.get_i(SPC_CHCOUNT) 349 350 def bits_per_sample(self) -> int: 351 """ 352 Get the number of bits per sample of the card (see register `SPC_MIINST_BITSPERSAMPLE` in the manual) 353 354 Returns 355 ------- 356 int 357 The number of bits per sample of the card 358 """ 359 360 return self.get_i(SPC_MIINST_BITSPERSAMPLE) 361 362 def bytes_per_sample(self) -> int: 363 """ 364 Get the number of bytes per sample 365 366 Returns 367 ------- 368 int 369 number of bytes per sample 370 """ 371 return self.get_i(SPC_MIINST_BYTESPERSAMPLE) 372 373 def max_sample_value(self) -> int: 374 """ 375 Get the maximum ADC value of the card (see register `SPC_MIINST_MAXADCVALUE` in the manual) 376 377 Returns 378 ------- 379 int 380 The maximum ADC value of the card 381 """ 382 383 return self._max_sample_value 384 385 def loops(self, loops : int = None) -> int: 386 """ 387 Set the number of times the memory is replayed. If set to zero the generation will run continuously until it is 388 stopped by the user. (see register `SPC_LOOPS` in the manual) 389 390 Parameters 391 ---------- 392 loops : int 393 the number of loops that the card should perform 394 """ 395 396 if loops is not None: 397 self.set_i(SPC_LOOPS, loops) 398 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.
46 def open(self, device_identifier : str = None) -> 'Card': 47 """ 48 Open a connection to the card 49 50 Parameters 51 ---------- 52 device_identifier : str = "" 53 The device identifier of the card that needs to be opened 54 55 Returns 56 ------- 57 Card 58 The card object 59 60 Raises 61 ------ 62 SpcmException 63 """ 64 65 if device_identifier is not None: 66 return super().open(device_identifier=device_identifier) 67 68 super().open() 69 70 # keyword arguments 71 card_type = self._kwargs.get("card_type", 0) 72 serial_number = self._kwargs.get("serial_number", 0) 73 74 if self.device_identifier == "": 75 # No device identifier was given, so we need to find the first card 76 self._handle = self.find(card_type=card_type, serial_number=serial_number) 77 if not self._handle: 78 if card_type: 79 raise SpcmException(text="No card found of right type") 80 elif serial_number: 81 raise SpcmException(text="No card found with serial number: {}".format(serial_number)) 82 else: 83 self._closed = False 84 elif self._handle: 85 if card_type != 0 and self.function_type() != card_type: 86 raise SpcmException(text="The card with the given device identifier is not the correct type") 87 elif serial_number != 0 and self.sn() != serial_number: 88 raise SpcmException(text="The card with the given device identifier does not have the correct serial number") 89 90 # Check python, driver and kernel version 91 if self._verbose: 92 print("Python version: {} on {}".format (platform.python_version(), platform.system())) 93 print("Driver version: {major}.{minor}.{build}".format(**self.drv_version())) 94 print("Kernel version: {major}.{minor}.{build}".format(**self.kernel_version())) 95 if self._handle: 96 print("Found '{}': {} sn {:05d}".format(self.device_identifier, self.product_name(), self.sn())) 97 98 # Get the function type of the card 99 self._function_type = self.get_i(SPC_FNCTYPE) 100 self._card_type = self.get_i(SPC_PCITYP) 101 self._features = self.get_i(SPC_PCIFEATURES) 102 self._ext_features = self.get_i(SPC_PCIEXTFEATURES) 103 self._max_sample_value = self.get_i(SPC_MIINST_MAXADCVALUE) 104 105 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
119 def find(self, card_type : int = 0, serial_number : int = 0) -> Union[bool, int]: 120 """ 121 Find first card that is connected to the computer, with either the given card type or serial number 122 123 Parameters 124 ---------- 125 card_type : int = 0 126 The function type of the card that needs to be found 127 serial_number : int = 0 128 The serial number of the card that needs to be found 129 130 Returns 131 ------- 132 Union[bool, int] 133 False if no card is found, otherwise the handle of the card 134 135 """ 136 for nr in range(self._max_cards): 137 device_identifier = self._std_device_identifier.format(nr) 138 handle = spcm_hOpen(ctypes.create_string_buffer(bytes(device_identifier, 'utf-8'))) 139 if handle: 140 self.device_identifier = device_identifier 141 return_value = ctypes.c_int64() 142 spcm_dwGetParam_i64(handle, SPC_FNCTYPE, ctypes.byref(return_value)) 143 function_type = return_value.value 144 spcm_dwGetParam_i64(handle, SPC_PCISERIALNO, ctypes.byref(return_value)) 145 sn = return_value.value 146 if card_type != 0 and (card_type & function_type) == function_type: 147 return handle 148 elif sn != 0 and sn == serial_number: 149 return handle 150 elif serial_number == 0 and card_type == 0: 151 return handle 152 spcm_vClose(handle) 153 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
157 def status(self) -> int: 158 """ 159 Get the status of the card (see register `SPC_M2STATUS` in the manual) 160 161 Returns 162 ------- 163 int 164 The status of the card 165 """ 166 167 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
169 def card_type(self) -> int: 170 """ 171 Get the card type of the card (see register `SPC_PCITYP` in the manual) 172 173 Returns 174 ------- 175 int 176 The card type of the card 177 """ 178 179 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
181 def series(self) -> int: 182 """ 183 Get the series of the card (see register `SPC_PCITYP` and `TYP_SERIESMASK` in the manual) 184 185 Returns 186 ------- 187 int 188 The series of the card 189 """ 190 191 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
193 def family(self) -> int: 194 """ 195 Get the family of the card (see register `SPC_PCITYP` and `TYP_FAMILYMASK` in the manual) 196 197 Returns 198 ------- 199 int 200 The family of the card 201 """ 202 203 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
205 def function_type(self) -> int: 206 """ 207 Gives information about what type of card it is. (see register `SPC_FNCTYPE` in the manual) 208 209 Returns 210 ------- 211 int 212 The function type of the card 213 214 * SPCM_TYPE_AI = 1h - Analog input card (analog acquisition; the M2i.4028 and M2i.4038 also return this value) 215 * SPCM_TYPE_AO = 2h - Analog output card (arbitrary waveform generators) 216 * SPCM_TYPE_DI = 4h - Digital input card (logic analyzer card) 217 * SPCM_TYPE_DO = 8h - Digital output card (pattern generators) 218 * SPCM_TYPE_DIO = 10h - Digital I/O (input/output) card, where the direction is software selectable. 219 """ 220 221 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.
223 def features(self) -> int: 224 """ 225 Get the features of the card (see register `SPC_PCIFEATURES` in the manual) 226 227 Returns 228 ------- 229 int 230 The features of the card 231 """ 232 233 return self._features
Get the features of the card (see register SPC_PCIFEATURES
in the manual)
Returns
- int: The features of the card
235 def ext_features(self) -> int: 236 """ 237 Get the extended features of the card (see register `SPC_PCIEXTFEATURES` in the manual) 238 239 Returns 240 ------- 241 int 242 The extended features of the card 243 """ 244 245 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
247 def starhub_card(self) -> bool: 248 """ 249 Check if the card is a starhub card (see register `SPC_PCIFEATURES` in the manual) 250 251 Returns 252 ------- 253 bool 254 True if the card is the card that carriers a starhub, False otherwise 255 """ 256 257 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
259 def num_modules(self) -> int: 260 """ 261 Get the number of modules of the card (see register `SPC_MIINST_MODULES` in the manual) 262 263 Returns 264 ------- 265 int 266 The number of modules of the card 267 """ 268 269 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
271 def channels_per_module(self) -> int: 272 """ 273 Get the number of channels per module of the card (see register `SPC_MIINST_CHPERMODULE` in the manual) 274 275 Returns 276 ------- 277 int 278 The number of channels per module of the card 279 """ 280 281 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
283 def num_channels(self) -> int: 284 """ 285 Get the number of channels of the card (= SPC_MIINST_MODULES * SPC_MIINST_CHPERMODULE) 286 287 Returns 288 ------- 289 int 290 The number of channels of the card 291 """ 292 293 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
295 def card_mode(self, card_mode : int = None) -> int: 296 """ 297 Set the card mode of the connected card (see register `SPC_CARDMODE` in the manual) 298 299 Parameters 300 ---------- 301 card_mode : int 302 the mode that the card needs to operate in 303 304 Returns 305 ------- 306 int 307 the mode that the card operates in 308 """ 309 310 if card_mode is not None: 311 self.set_i(SPC_CARDMODE, card_mode) 312 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
314 def product_name(self) -> str: 315 """ 316 Get the product name of the card (see register `SPC_PCITYP` in the manual) 317 318 Returns 319 ------- 320 str 321 The product name of the connected card (e.g. M4i.6631-x8) 322 """ 323 324 return self.get_str(SPC_PCITYP)
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)
326 def sn(self) -> int: 327 """ 328 Get the serial number of a product (see register `SPC_PCISERIALNO` in the manual) 329 330 Returns 331 ------- 332 int 333 The serial number of the connected card (e.g. 12345) 334 """ 335 336 return self.get_i(SPC_PCISERIALNO)
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)
338 def active_channels(self) -> int: 339 """ 340 Get the number of channels of the card (see register `SPC_CHCOUNT` in the manual) 341 342 Returns 343 ------- 344 int 345 The number of channels of the card 346 """ 347 348 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
350 def bits_per_sample(self) -> int: 351 """ 352 Get the number of bits per sample of the card (see register `SPC_MIINST_BITSPERSAMPLE` in the manual) 353 354 Returns 355 ------- 356 int 357 The number of bits per sample of the card 358 """ 359 360 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
362 def bytes_per_sample(self) -> int: 363 """ 364 Get the number of bytes per sample 365 366 Returns 367 ------- 368 int 369 number of bytes per sample 370 """ 371 return self.get_i(SPC_MIINST_BYTESPERSAMPLE)
Get the number of bytes per sample
Returns
- int: number of bytes per sample
373 def max_sample_value(self) -> int: 374 """ 375 Get the maximum ADC value of the card (see register `SPC_MIINST_MAXADCVALUE` in the manual) 376 377 Returns 378 ------- 379 int 380 The maximum ADC value of the card 381 """ 382 383 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
385 def loops(self, loops : int = None) -> int: 386 """ 387 Set the number of times the memory is replayed. If set to zero the generation will run continuously until it is 388 stopped by the user. (see register `SPC_LOOPS` in the manual) 389 390 Parameters 391 ---------- 392 loops : int 393 the number of loops that the card should perform 394 """ 395 396 if loops is not None: 397 self.set_i(SPC_LOOPS, loops) 398 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
499class Channels: 500 """ 501 a higher-level abstraction of the CardFunctionality class to implement the Card's channel settings 502 """ 503 504 cards : list[Card] = [] 505 channels : list[Channel] = [] 506 num_channels : list[int] = [] 507 508 def __init__(self, card : Card = None, card_enable : int = None, stack : CardStack = None, stack_enable : list[int] = None) -> None: 509 """ 510 Constructor of the Channels class 511 512 Parameters 513 ---------- 514 card : Card = None 515 The card to be used 516 card_enable : int = None 517 The bitmask to enable specific channels 518 stack : CardStack = None 519 The card stack to be used 520 stack_enable : list[int] = None 521 The list of bitmasks to enable specific channels 522 523 Raises 524 ------ 525 SpcmException 526 No card or card stack provided 527 """ 528 529 self.cards = [] 530 self.channels = [] 531 self.num_channels = [] 532 if card is not None: 533 self.cards.append(card) 534 if card_enable is not None: 535 self.channels_enable(enable_list=[card_enable]) 536 else: 537 self.channels_enable(enable_all=True) 538 elif stack is not None: 539 self.cards = stack.cards 540 if stack_enable is not None: 541 self.channels_enable(enable_list=stack_enable) 542 else: 543 self.channels_enable(enable_all=True) 544 else: 545 raise SpcmException(text="No card or card stack provided") 546 547 def __str__(self) -> str: 548 """ 549 String representation of the Channels class 550 551 Returns 552 ------- 553 str 554 String representation of the Channels class 555 """ 556 557 return f"Channels()" 558 559 __repr__ = __str__ 560 561 def __iter__(self) -> "Channels": 562 """Define this class as an iterator""" 563 return self 564 565 def __getitem__(self, index : int) -> Channel: 566 """ 567 This method is called to access the channel by index 568 569 Parameters 570 ---------- 571 index : int 572 The index of the channel 573 574 Returns 575 ------- 576 Channel 577 the channel at the specific index 578 """ 579 580 if index < 0 or index >= len(self.channels): 581 raise IndexError(repr(index)) 582 return self.channels[index] 583 584 _channel_iterator_index = -1 585 def __next__(self) -> Channel: 586 """ 587 This method is called when the next element is requested from the iterator 588 589 Returns 590 ------- 591 Channel 592 the next available channel 593 594 Raises 595 ------ 596 StopIteration 597 """ 598 self._channel_iterator_index += 1 599 if self._channel_iterator_index >= len(self.channels): 600 self._channel_iterator_index = -1 601 raise StopIteration 602 return self.channels[self._channel_iterator_index] 603 604 def __len__(self) -> int: 605 """Returns the number of channels""" 606 return len(self.channels) 607 608 def write_setup(self) -> None: 609 """Write the setup to the card""" 610 for card in self.cards: 611 card.write_setup() 612 613 def channels_enable(self, enable_list : list[int] = None, enable_all : bool = False) -> int: 614 """ 615 Enables or disables the channels of all the available cards (see register `SPC_CHENABLE` in the manual) 616 617 Parameters 618 ---------- 619 enable_list : list[int] = None 620 A list of channels bitmasks to be enable or disable specific channels 621 enable_all : bool = False 622 Enable all the channels 623 624 Returns 625 ------- 626 int 627 A list with items that indicate for each card the number of channels that are enabled, or True to enable all channels. 628 """ 629 630 self.channels = [] 631 self.num_channels = [] 632 num_channels = 0 633 634 if enable_all: 635 for card in self.cards: 636 num_channels = card.num_channels() 637 card.set_i(SPC_CHENABLE, (1 << num_channels) - 1) 638 num_channels = card.get_i(SPC_CHCOUNT) 639 self.num_channels.append(num_channels) 640 for i in range(num_channels): 641 self.channels.append(Channel(i, i, card)) 642 elif enable_list is not None: 643 for enable, card in zip(enable_list, self.cards): 644 card.set_i(SPC_CHENABLE, enable) 645 num_channels = card.get_i(SPC_CHCOUNT) 646 self.num_channels.append(num_channels) 647 counter = 0 648 for i in range(len(bin(enable))): 649 if (enable >> i) & 1: 650 self.channels.append(Channel(i, counter, card)) 651 counter += 1 652 return sum(self.num_channels) 653 654 # def __getattribute__(self, name): 655 # # print("Calling __getattr__: {}".format(name)) 656 # if hasattr(Channel, name): 657 # def wrapper(*args, **kw): 658 # for channel in self.channels: 659 # getattr(channel, name)(*args, **kw) 660 # return wrapper 661 # else: 662 # return object.__getattribute__(self, name) 663 664 def enable(self, enable : bool) -> None: 665 """ 666 Enables or disables the analog front-end of all channels of the card (see register `SPC_ENABLEOUT` in the manual) 667 668 Parameters 669 ---------- 670 enable : bool 671 Turn-on (True) or off (False) the spezific channel 672 """ 673 674 for channel in self.channels: 675 channel.enable(enable) 676 enable_out = enable 677 678 def path(self, value : int) -> None: 679 """ 680 Sets the input path of the analog front-end of all channels of the card (see register `SPC_PATH` in the manual) 681 682 Parameters 683 ---------- 684 value : int 685 The input path of the specific channel 686 """ 687 688 for channel in self.channels: 689 channel.path(value) 690 691 def amp(self, value : int) -> None: 692 """ 693 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) 694 695 Parameters 696 ---------- 697 value : int 698 The output range (amplitude) of all channels in millivolts 699 """ 700 701 for channel in self.channels: 702 channel.amp(value) 703 704 def offset(self, value : int) -> None: 705 """ 706 Sets the offset of the analog front-end of all channels of the card in mV (see register `SPC_OFFSET` in the manual) 707 708 Parameters 709 ---------- 710 value : int 711 The offset of all channels in millivolts 712 """ 713 714 for channel in self.channels: 715 channel.offset(value) 716 717 def termination(self, value : int) -> None: 718 """ 719 Sets the termination of the analog front-end of all channels of the card (see register `SPC_50OHM` in the manual) 720 721 Parameters 722 ---------- 723 value : int 724 The termination of all channels 725 """ 726 727 for channel in self.channels: 728 channel.termination(value) 729 730 def digital_termination(self, word_id : int, value : int) -> None: 731 """ 732 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) 733 734 Parameters 735 ---------- 736 word_id : int 737 The ID of the word of channels (e.g. 0 = D15 - D0, 1 = D31 - D16) 738 value : bool | int 739 The termination of all channels (0 = high-Z, 1 = 110 Ohm) 740 """ 741 742 for card in self.cards: 743 card.set_i(SPC_110OHM0 + word_id * (SPC_110OHM1 - SPC_110OHM0), int(value)) 744 745 def coupling(self, value : int) -> None: 746 """ 747 Sets the coupling of the analog front-end of all channels of the card (see register `SPC_ACDC` in the manual) 748 749 Parameters 750 ---------- 751 value : int 752 The coupling of all channels 753 """ 754 755 for channel in self.channels: 756 channel.coupling(value) 757 758 def coupling_offset_compensation(self, value : int) -> None: 759 """ 760 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) 761 762 Parameters 763 ---------- 764 value : int 765 The coupling offset compensation of all channels 766 """ 767 768 for channel in self.channels: 769 channel.coupling_offset_compensation(value) 770 771 def filter(self, value : int) -> None: 772 """ 773 Sets the filter of the analog front-end of all channels of the card (see register `SPC_FILTER` in the manual) 774 775 Parameters 776 ---------- 777 value : int 778 The filter of all channels 779 """ 780 781 for channel in self.channels: 782 channel.filter(value) 783 784 def stop_level(self, value : int) -> None: 785 """ 786 Usually the used outputs of the analog generation boards are set to zero level after replay. 787 This is in most cases adequate. In some cases it can be necessary to hold the last sample, 788 to output the maximum positive level or maximum negative level after replay. The stoplevel will 789 stay on the defined level until the next output has been made. With this function 790 you can define the behavior after replay (see register `SPC_CH0_STOPLEVEL` in the manual) 791 792 Parameters 793 ---------- 794 value : int 795 The wanted stop behaviour: 796 """ 797 798 for channel in self.channels: 799 channel.stop_level(value) 800 801 def custom_stop(self, value : int) -> None: 802 """ 803 Allows to define a 16bit wide custom level per channel for the analog output to enter in pauses. The sample format is 804 exactly the same as during replay, as described in the „sample format“ section. 805 When synchronous digital bits are replayed along, the custom level must include these as well and therefore allows to 806 set a custom level for each multi-purpose line separately. (see register `SPC_CH0_CUSTOM_STOP` in the manual) 807 808 Parameters 809 ---------- 810 value : int 811 The custom stop value 812 """ 813 814 for channel in self.channels: 815 channel.custom_stop(value) 816 817 def output_load(self, value : pint.Quantity) -> None: 818 """ 819 Sets the electrical load of the user system connect the channel of the card. This is important for the correct 820 calculation of the output power. Typically, the load would be 50 Ohms, but it can be different. 821 822 Parameters 823 ---------- 824 value : pint.Quantity 825 The electrical load connected by the user to the specific channel 826 """ 827 for channel in self.channels: 828 channel.output_load(value) 829 830 def ch_mask(self) -> int: 831 """ 832 Gets mask for the "or"- or "and"-mask 833 834 Returns 835 ------- 836 int 837 The mask for the "or"- or "and"-mask 838 """ 839 840 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
508 def __init__(self, card : Card = None, card_enable : int = None, stack : CardStack = None, stack_enable : list[int] = None) -> None: 509 """ 510 Constructor of the Channels class 511 512 Parameters 513 ---------- 514 card : Card = None 515 The card to be used 516 card_enable : int = None 517 The bitmask to enable specific channels 518 stack : CardStack = None 519 The card stack to be used 520 stack_enable : list[int] = None 521 The list of bitmasks to enable specific channels 522 523 Raises 524 ------ 525 SpcmException 526 No card or card stack provided 527 """ 528 529 self.cards = [] 530 self.channels = [] 531 self.num_channels = [] 532 if card is not None: 533 self.cards.append(card) 534 if card_enable is not None: 535 self.channels_enable(enable_list=[card_enable]) 536 else: 537 self.channels_enable(enable_all=True) 538 elif stack is not None: 539 self.cards = stack.cards 540 if stack_enable is not None: 541 self.channels_enable(enable_list=stack_enable) 542 else: 543 self.channels_enable(enable_all=True) 544 else: 545 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
608 def write_setup(self) -> None: 609 """Write the setup to the card""" 610 for card in self.cards: 611 card.write_setup()
Write the setup to the card
613 def channels_enable(self, enable_list : list[int] = None, enable_all : bool = False) -> int: 614 """ 615 Enables or disables the channels of all the available cards (see register `SPC_CHENABLE` in the manual) 616 617 Parameters 618 ---------- 619 enable_list : list[int] = None 620 A list of channels bitmasks to be enable or disable specific channels 621 enable_all : bool = False 622 Enable all the channels 623 624 Returns 625 ------- 626 int 627 A list with items that indicate for each card the number of channels that are enabled, or True to enable all channels. 628 """ 629 630 self.channels = [] 631 self.num_channels = [] 632 num_channels = 0 633 634 if enable_all: 635 for card in self.cards: 636 num_channels = card.num_channels() 637 card.set_i(SPC_CHENABLE, (1 << num_channels) - 1) 638 num_channels = card.get_i(SPC_CHCOUNT) 639 self.num_channels.append(num_channels) 640 for i in range(num_channels): 641 self.channels.append(Channel(i, i, card)) 642 elif enable_list is not None: 643 for enable, card in zip(enable_list, self.cards): 644 card.set_i(SPC_CHENABLE, enable) 645 num_channels = card.get_i(SPC_CHCOUNT) 646 self.num_channels.append(num_channels) 647 counter = 0 648 for i in range(len(bin(enable))): 649 if (enable >> i) & 1: 650 self.channels.append(Channel(i, counter, card)) 651 counter += 1 652 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.
664 def enable(self, enable : bool) -> None: 665 """ 666 Enables or disables the analog front-end of all channels of the card (see register `SPC_ENABLEOUT` in the manual) 667 668 Parameters 669 ---------- 670 enable : bool 671 Turn-on (True) or off (False) the spezific channel 672 """ 673 674 for channel in self.channels: 675 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
664 def enable(self, enable : bool) -> None: 665 """ 666 Enables or disables the analog front-end of all channels of the card (see register `SPC_ENABLEOUT` in the manual) 667 668 Parameters 669 ---------- 670 enable : bool 671 Turn-on (True) or off (False) the spezific channel 672 """ 673 674 for channel in self.channels: 675 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
678 def path(self, value : int) -> None: 679 """ 680 Sets the input path of the analog front-end of all channels of the card (see register `SPC_PATH` in the manual) 681 682 Parameters 683 ---------- 684 value : int 685 The input path of the specific channel 686 """ 687 688 for channel in self.channels: 689 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
691 def amp(self, value : int) -> None: 692 """ 693 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) 694 695 Parameters 696 ---------- 697 value : int 698 The output range (amplitude) of all channels in millivolts 699 """ 700 701 for channel in self.channels: 702 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
704 def offset(self, value : int) -> None: 705 """ 706 Sets the offset of the analog front-end of all channels of the card in mV (see register `SPC_OFFSET` in the manual) 707 708 Parameters 709 ---------- 710 value : int 711 The offset of all channels in millivolts 712 """ 713 714 for channel in self.channels: 715 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
717 def termination(self, value : int) -> None: 718 """ 719 Sets the termination of the analog front-end of all channels of the card (see register `SPC_50OHM` in the manual) 720 721 Parameters 722 ---------- 723 value : int 724 The termination of all channels 725 """ 726 727 for channel in self.channels: 728 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
730 def digital_termination(self, word_id : int, value : int) -> None: 731 """ 732 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) 733 734 Parameters 735 ---------- 736 word_id : int 737 The ID of the word of channels (e.g. 0 = D15 - D0, 1 = D31 - D16) 738 value : bool | int 739 The termination of all channels (0 = high-Z, 1 = 110 Ohm) 740 """ 741 742 for card in self.cards: 743 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)
745 def coupling(self, value : int) -> None: 746 """ 747 Sets the coupling of the analog front-end of all channels of the card (see register `SPC_ACDC` in the manual) 748 749 Parameters 750 ---------- 751 value : int 752 The coupling of all channels 753 """ 754 755 for channel in self.channels: 756 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
758 def coupling_offset_compensation(self, value : int) -> None: 759 """ 760 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) 761 762 Parameters 763 ---------- 764 value : int 765 The coupling offset compensation of all channels 766 """ 767 768 for channel in self.channels: 769 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
771 def filter(self, value : int) -> None: 772 """ 773 Sets the filter of the analog front-end of all channels of the card (see register `SPC_FILTER` in the manual) 774 775 Parameters 776 ---------- 777 value : int 778 The filter of all channels 779 """ 780 781 for channel in self.channels: 782 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
784 def stop_level(self, value : int) -> None: 785 """ 786 Usually the used outputs of the analog generation boards are set to zero level after replay. 787 This is in most cases adequate. In some cases it can be necessary to hold the last sample, 788 to output the maximum positive level or maximum negative level after replay. The stoplevel will 789 stay on the defined level until the next output has been made. With this function 790 you can define the behavior after replay (see register `SPC_CH0_STOPLEVEL` in the manual) 791 792 Parameters 793 ---------- 794 value : int 795 The wanted stop behaviour: 796 """ 797 798 for channel in self.channels: 799 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:
801 def custom_stop(self, value : int) -> None: 802 """ 803 Allows to define a 16bit wide custom level per channel for the analog output to enter in pauses. The sample format is 804 exactly the same as during replay, as described in the „sample format“ section. 805 When synchronous digital bits are replayed along, the custom level must include these as well and therefore allows to 806 set a custom level for each multi-purpose line separately. (see register `SPC_CH0_CUSTOM_STOP` in the manual) 807 808 Parameters 809 ---------- 810 value : int 811 The custom stop value 812 """ 813 814 for channel in self.channels: 815 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
817 def output_load(self, value : pint.Quantity) -> None: 818 """ 819 Sets the electrical load of the user system connect the channel of the card. This is important for the correct 820 calculation of the output power. Typically, the load would be 50 Ohms, but it can be different. 821 822 Parameters 823 ---------- 824 value : pint.Quantity 825 The electrical load connected by the user to the specific channel 826 """ 827 for channel in self.channels: 828 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
830 def ch_mask(self) -> int: 831 """ 832 Gets mask for the "or"- or "and"-mask 833 834 Returns 835 ------- 836 int 837 The mask for the "or"- or "and"-mask 838 """ 839 840 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_offset : pint.Quantity = None 27 _output_load : pint.Quantity = None 28 _series_impedance : pint.Quantity = None 29 30 def __init__(self, index : int, data_index : int, card : Card) -> None: 31 """ 32 Constructor of the Channel class 33 34 Parameters 35 ---------- 36 index : int 37 The index of the channel 38 card : Card 39 The card of the channel 40 """ 41 42 self.card = card 43 self.index = index 44 self.data_index = data_index 45 self._conversion_amp = None 46 self._conversion_offset = 0 * units.percent 47 self._output_load = 50 * units.ohm 48 self._series_impedance = 50 * units.ohm 49 50 def __str__(self) -> str: 51 """ 52 String representation of the Channel class 53 54 Returns 55 ------- 56 str 57 String representation of the Channel class 58 """ 59 60 return f"Channel {self.index}" 61 62 __repr__ = __str__ 63 64 def __int__(self) -> int: 65 """ 66 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 67 68 Returns 69 ------- 70 int 71 The index of the channel 72 """ 73 return self.data_index 74 __index__ = __int__ 75 76 def __add__(self, other): 77 """ 78 The Channel object again acts like an int and returns the index of the channel plus the other value 79 80 Parameters 81 ---------- 82 other : int or float 83 The value to be added to the index of the channel 84 85 Returns 86 ------- 87 int or float 88 The index of the channel plus the other value 89 """ 90 return self.index + other 91 92 def enable(self, enable : bool = None) -> bool: 93 """ 94 Enables the analog front-end of the channel of the card (see register `SPC_ENABLEOUT` in the manual) 95 96 Parameters 97 ---------- 98 enable : bool 99 Turn-on (True) or off (False) the spezific channel 100 101 Returns 102 ------- 103 bool 104 The enable state of the specific channel 105 """ 106 107 if enable is not None: 108 self.card.set_i(SPC_ENABLEOUT0 + (SPC_ENABLEOUT1 - SPC_ENABLEOUT0) * self.index, int(enable)) 109 return bool(self.card.get_i(SPC_ENABLEOUT0 + (SPC_ENABLEOUT1 - SPC_ENABLEOUT0) * self.index)) 110 enable_out = enable 111 112 def path(self, value : int = None) -> int: 113 """ 114 Sets the input path of the channel of the card (see register `SPC_PATH0` in the manual). 115 To make the read back of the coupling offset compensation setting possible, we also set 116 the register `SPC_READAIPATH` to the same value. 117 118 Parameters 119 ---------- 120 value : int 121 The input path of the specific channel 122 123 Returns 124 ------- 125 int 126 The input path of the specific channel 127 """ 128 129 if value is not None: 130 self.card.set_i(SPC_PATH0 + (SPC_PATH1 - SPC_PATH0) * self.index, value) 131 self.card.set_i(SPC_READAIPATH, value) 132 return self.card.get_i(SPC_PATH0 + (SPC_PATH1 - SPC_PATH0) * self.index) 133 134 def amp(self, value : int = None, return_unit = None) -> int: 135 """ 136 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) 137 138 Parameters 139 ---------- 140 value : int 141 The output range (amplitude) of the specific channel in millivolts 142 unit : pint.Unit = None 143 The unit of the return value 144 145 Returns 146 ------- 147 int | pint.Quantity 148 The output range (amplitude) of the specific channel in millivolts or the unit specified 149 """ 150 151 if value is not None: 152 if isinstance(value, pint.Quantity): 153 value = self.voltage_conversion(value) 154 self._conversion_amp = UnitConversion.force_unit(value, units.mV) 155 value = UnitConversion.convert(value, units.mV, int) 156 self.card.set_i(SPC_AMP0 + (SPC_AMP1 - SPC_AMP0) * self.index, value) 157 value = self.card.get_i(SPC_AMP0 + (SPC_AMP1 - SPC_AMP0) * self.index) 158 value = UnitConversion.to_unit(value * units.mV, return_unit) 159 return value 160 161 def offset(self, value : int = None, return_unit = None) -> int: 162 """ 163 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) 164 If the value is given and has a unit, then this unit is converted to the unit of the card (mV or %) 165 166 Parameters 167 ---------- 168 value : int | pint.Quantity = None 169 The offset of the specific channel as integer in % or as a Quantity in % or mV 170 unit : pint.Unit = None 171 The unit of the return value 172 173 Returns 174 ------- 175 int | pint.Quantity 176 The offset of the specific channel in % or the unit specified by return_unit 177 """ 178 179 # 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) 180 card_unit = 1 181 fnc_type = self.card.function_type() 182 if fnc_type == SPCM_TYPE_AI: 183 card_unit = units.percent 184 elif fnc_type == SPCM_TYPE_AO: 185 card_unit = units.mV 186 187 if value is not None: 188 # The user gives a value as a Quantity 189 if isinstance(value, pint.Quantity): 190 if fnc_type == SPCM_TYPE_AO: 191 # The card expects a value in mV 192 if value.check('[]'): 193 # Convert from percent to mV 194 value = (value * self._conversion_amp).to(card_unit) 195 else: 196 value = value.to(card_unit) 197 elif fnc_type == SPCM_TYPE_AI: 198 # The card expects a value in percent 199 if value.check('[electric_potential]'): 200 # Convert from mV to percent 201 value = (value / self._conversion_amp).to(card_unit) 202 else: 203 value = value.to(card_unit) 204 else: 205 # Value is given as a number 206 pass 207 208 value = UnitConversion.convert(value, card_unit, int) 209 self.card.set_i(SPC_OFFS0 + (SPC_OFFS1 - SPC_OFFS0) * self.index, value) 210 211 return_value = self.card.get_i(SPC_OFFS0 + (SPC_OFFS1 - SPC_OFFS0) * self.index) 212 # Turn the return value into a quantity 213 return_quantity = UnitConversion.to_unit(return_value, return_unit) 214 # Save the conversion offset to be able to convert the data to a quantity with the correct unit 215 self._conversion_offset = UnitConversion.force_unit(return_value, card_unit) 216 return return_quantity 217 218 def convert_data(self, data : npt.NDArray, return_unit : pint.Unit = units.mV, averages : int = 1) -> npt.NDArray: 219 """ 220 Converts the data to the correct unit in units of electrical potential 221 222 Parameters 223 ---------- 224 data : numpy.ndarray 225 The data to be converted 226 return_unit : pint.Unit = units.mV 227 The unit of the return value 228 averages : int = 1 229 The number of averages that have been done to the data and should be taken into account to convert the data 230 231 Returns 232 ------- 233 numpy.ndarray 234 The converted data in units of electrical potential 235 """ 236 237 max_value = self.card.max_sample_value() * averages 238 if self._conversion_offset.check('[]'): 239 return_data = (data / max_value - self._conversion_offset) * self._conversion_amp 240 else: 241 return_data = (data / max_value) * self._conversion_amp - self._conversion_offset 242 return_data = UnitConversion.to_unit(return_data, return_unit) 243 return return_data 244 245 def reconvert_data(self, data : npt.NDArray) -> npt.NDArray: 246 """ 247 Convert data with units back to integer values in units of electrical potential 248 249 Parameters 250 ---------- 251 data : numpy.ndarray 252 The data to be reconverted 253 254 Returns 255 ------- 256 numpy.ndarray 257 The reconverted data as integer in mV 258 """ 259 260 if self._conversion_offset.check('[]'): 261 return_data = int((data / self._conversion_amp + self._conversion_offset) * self.card.max_sample_value()) 262 else: 263 return_data = int(((data + self._conversion_offset) / self._conversion_amp) * self.card.max_sample_value()) 264 return return_data 265 266 def termination(self, value : int) -> None: 267 """ 268 Sets the termination of the analog front-end of the channel of the card (see register `SPC_50OHM0` in the manual) 269 270 Parameters 271 ---------- 272 value : int | bool 273 The termination of the specific channel 274 """ 275 276 self.card.set_i(SPC_50OHM0 + (SPC_50OHM1 - SPC_50OHM0) * self.index, int(value)) 277 278 def get_termination(self) -> int: 279 """ 280 Gets the termination of the analog front-end of the channel of the card (see register `SPC_50OHM0` in the manual) 281 282 Returns 283 ------- 284 int 285 The termination of the specific channel 286 """ 287 288 return self.card.get_i(SPC_50OHM0 + (SPC_50OHM1 - SPC_50OHM0) * self.index) 289 290 def coupling(self, value : int = None) -> int: 291 """ 292 Sets the coupling of the analog front-end of the channel of the card (see register `SPC_ACDC0` in the manual) 293 294 Parameters 295 ---------- 296 value : int 297 The coupling of the specific channel 298 299 Returns 300 ------- 301 int 302 The coupling of the specific channel 303 """ 304 305 if value is not None: 306 self.card.set_i(SPC_ACDC0 + (SPC_ACDC1 - SPC_ACDC0) * self.index, value) 307 return self.card.get_i(SPC_ACDC0 + (SPC_ACDC1 - SPC_ACDC0) * self.index) 308 309 def coupling_offset_compensation(self, value : int = None) -> int: 310 """ 311 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) 312 313 Parameters 314 ---------- 315 value : int 316 Enables the coupling offset compensation of the specific channel 317 318 Returns 319 ------- 320 int 321 return if the coupling offset compensation of the specific channel is enabled ("1") or disabled ("0") 322 """ 323 324 if value is not None: 325 self.card.set_i(SPC_ACDC_OFFS_COMPENSATION0 + (SPC_ACDC_OFFS_COMPENSATION1 - SPC_ACDC_OFFS_COMPENSATION0) * self.index, value) 326 return self.card.get_i(SPC_ACDC_OFFS_COMPENSATION0 + (SPC_ACDC_OFFS_COMPENSATION1 - SPC_ACDC_OFFS_COMPENSATION0) * self.index) 327 328 def filter(self, value : int = None) -> int: 329 """ 330 Sets the filter of the analog front-end of the channel of the card (see register `SPC_FILTER0` in the manual) 331 332 Parameters 333 ---------- 334 value : int 335 The filter of the specific channel 336 337 Returns 338 ------- 339 int 340 The filter of the specific channel 341 """ 342 343 if value is not None: 344 self.card.set_i(SPC_FILTER0 + (SPC_FILTER1 - SPC_FILTER0) * self.index, value) 345 return self.card.get_i(SPC_FILTER0 + (SPC_FILTER1 - SPC_FILTER0) * self.index) 346 347 def stop_level(self, value : int = None) -> int: 348 """ 349 Usually the used outputs of the analog generation boards are set to zero level after replay. 350 This is in most cases adequate. In some cases it can be necessary to hold the last sample, 351 to output the maximum positive level or maximum negative level after replay. The stoplevel will 352 stay on the defined level until the next output has been made. With this function 353 you can define the behavior after replay (see register `SPC_CH0_STOPLEVEL` in the manual) 354 355 Parameters 356 ---------- 357 value : int 358 The wanted stop behaviour 359 360 Returns 361 ------- 362 int 363 The stop behaviour of the specific channel 364 """ 365 366 if value is not None: 367 self.card.set_i(SPC_CH0_STOPLEVEL + self.index * (SPC_CH1_STOPLEVEL - SPC_CH0_STOPLEVEL), value) 368 return self.card.get_i(SPC_CH0_STOPLEVEL + self.index * (SPC_CH1_STOPLEVEL - SPC_CH0_STOPLEVEL)) 369 370 def custom_stop(self, value : int = None) -> int: 371 """ 372 Allows to define a 16bit wide custom level per channel for the analog output to enter in pauses. The sample format is 373 exactly the same as during replay, as described in the „sample format“ section. 374 When synchronous digital bits are replayed along, the custom level must include these as well and therefore allows to 375 set a custom level for each multi-purpose line separately. (see register `SPC_CH0_CUSTOM_STOP` in the manual) 376 377 Parameters 378 ---------- 379 value : int 380 The custom stop value 381 382 Returns 383 ------- 384 int 385 The custom stop value of the specific channel 386 387 TODO: change this to a specific unit? 388 """ 389 390 if value is not None: 391 self.card.set_i(SPC_CH0_CUSTOM_STOP + self.index * (SPC_CH1_CUSTOM_STOP - SPC_CH0_CUSTOM_STOP), value) 392 return self.card.get_i(SPC_CH0_CUSTOM_STOP + self.index * (SPC_CH1_CUSTOM_STOP - SPC_CH0_CUSTOM_STOP)) 393 394 def ch_mask(self) -> int: 395 """ 396 Gets mask for the "or"- or "and"-mask 397 398 Returns 399 ------- 400 int 401 The mask for the "or"- or "and"-mask 402 """ 403 404 return 1 << self.index 405 406 def output_load(self, value : pint.Quantity = None) -> pint.Quantity: 407 """ 408 Sets the electrical load of the user system connect the channel of the card. This is important for the correct 409 calculation of the output power. Typically, the load would be 50 Ohms, but it can be different. 410 411 Parameters 412 ---------- 413 value : pint.Quantity 414 The electrical load connected by the user to the specific channel 415 416 Returns 417 ------- 418 pint.Quantity 419 The electrical load connected by the user to the specific channel 420 """ 421 if value is not None: 422 self._output_load = value 423 return self._output_load 424 425 def voltage_conversion(self, value : pint.Quantity) -> pint.Quantity: 426 """ 427 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 428 429 Parameters 430 ---------- 431 value : pint.Quantity 432 The voltage that is needed at a certain output load 433 434 Returns 435 ------- 436 pint.Quantity 437 The corresponding voltage at an output load of 50 Ohm 438 """ 439 440 # The two at the end is because the value expected by the card is defined for a 50 Ohm load 441 if self._output_load == np.inf * units.ohm: 442 return value / 2 443 return value / (self._output_load / (self._output_load + self._series_impedance)) / 2 444 445 def to_amplitude_fraction(self, value) -> float: 446 """ 447 Convert the voltage, percentage or power to percentage of the full range of the card 448 449 Parameters 450 ---------- 451 value : pint.Quantity | float 452 The voltage that should be outputted at a certain output load 453 454 Returns 455 ------- 456 float 457 The corresponding fraction of the full range of the card 458 """ 459 460 if isinstance(value, units.Quantity) and value.check("[power]"): 461 # U_pk = U_rms * sqrt(2) 462 value = np.sqrt(2 * value.to('mW') * self._output_load) / self._conversion_amp * 100 * units.percent 463 elif isinstance(value, units.Quantity) and value.check("[electric_potential]"): 464 # value in U_pk 465 value = self.voltage_conversion(value) / self._conversion_amp * 100 * units.percent 466 value = UnitConversion.convert(value, units.fraction, float, rounding=None) 467 return value 468 469 def from_amplitude_fraction(self, fraction, return_unit : pint.Quantity = None) -> pint.Quantity: 470 """ 471 Convert the percentage of the full range to voltage, percentage or power 472 473 Parameters 474 ---------- 475 fraction : float 476 The percentage of the full range of the card 477 return_unit : pint.Quantity 478 The unit of the return value 479 480 Returns 481 ------- 482 pint.Quantity 483 The corresponding voltage, percentage or power 484 """ 485 486 return_value = fraction 487 if isinstance(return_unit, units.Unit) and (1*return_unit).check("[power]"): 488 return_value = (np.power(self._conversion_amp * fraction, 2) / self._output_load / 2).to(return_unit) 489 # U_pk = U_rms * sqrt(2) 490 elif isinstance(return_unit, units.Unit) and (1*return_unit).check("[electric_potential]"): 491 return_value = (self._conversion_amp * fraction / (100 * units.percent)).to(return_unit) 492 # value in U_pk 493 # value = self.voltage_conversion(value) / self._conversion_amp * 100 * units.percent 494 elif isinstance(return_unit, units.Unit) and (1*return_unit).check("[]"): 495 return_value = UnitConversion.force_unit(fraction, return_unit) 496 return return_value
A class to represent a channel of a card only used inside the Channels class in the list of channels
30 def __init__(self, index : int, data_index : int, card : Card) -> None: 31 """ 32 Constructor of the Channel class 33 34 Parameters 35 ---------- 36 index : int 37 The index of the channel 38 card : Card 39 The card of the channel 40 """ 41 42 self.card = card 43 self.index = index 44 self.data_index = data_index 45 self._conversion_amp = None 46 self._conversion_offset = 0 * units.percent 47 self._output_load = 50 * units.ohm 48 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
92 def enable(self, enable : bool = None) -> bool: 93 """ 94 Enables the analog front-end of the channel of the card (see register `SPC_ENABLEOUT` in the manual) 95 96 Parameters 97 ---------- 98 enable : bool 99 Turn-on (True) or off (False) the spezific channel 100 101 Returns 102 ------- 103 bool 104 The enable state of the specific channel 105 """ 106 107 if enable is not None: 108 self.card.set_i(SPC_ENABLEOUT0 + (SPC_ENABLEOUT1 - SPC_ENABLEOUT0) * self.index, int(enable)) 109 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
92 def enable(self, enable : bool = None) -> bool: 93 """ 94 Enables the analog front-end of the channel of the card (see register `SPC_ENABLEOUT` in the manual) 95 96 Parameters 97 ---------- 98 enable : bool 99 Turn-on (True) or off (False) the spezific channel 100 101 Returns 102 ------- 103 bool 104 The enable state of the specific channel 105 """ 106 107 if enable is not None: 108 self.card.set_i(SPC_ENABLEOUT0 + (SPC_ENABLEOUT1 - SPC_ENABLEOUT0) * self.index, int(enable)) 109 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
112 def path(self, value : int = None) -> int: 113 """ 114 Sets the input path of the channel of the card (see register `SPC_PATH0` in the manual). 115 To make the read back of the coupling offset compensation setting possible, we also set 116 the register `SPC_READAIPATH` to the same value. 117 118 Parameters 119 ---------- 120 value : int 121 The input path of the specific channel 122 123 Returns 124 ------- 125 int 126 The input path of the specific channel 127 """ 128 129 if value is not None: 130 self.card.set_i(SPC_PATH0 + (SPC_PATH1 - SPC_PATH0) * self.index, value) 131 self.card.set_i(SPC_READAIPATH, value) 132 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
134 def amp(self, value : int = None, return_unit = None) -> int: 135 """ 136 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) 137 138 Parameters 139 ---------- 140 value : int 141 The output range (amplitude) of the specific channel in millivolts 142 unit : pint.Unit = None 143 The unit of the return value 144 145 Returns 146 ------- 147 int | pint.Quantity 148 The output range (amplitude) of the specific channel in millivolts or the unit specified 149 """ 150 151 if value is not None: 152 if isinstance(value, pint.Quantity): 153 value = self.voltage_conversion(value) 154 self._conversion_amp = UnitConversion.force_unit(value, units.mV) 155 value = UnitConversion.convert(value, units.mV, int) 156 self.card.set_i(SPC_AMP0 + (SPC_AMP1 - SPC_AMP0) * self.index, value) 157 value = self.card.get_i(SPC_AMP0 + (SPC_AMP1 - SPC_AMP0) * self.index) 158 value = UnitConversion.to_unit(value * units.mV, return_unit) 159 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
161 def offset(self, value : int = None, return_unit = None) -> int: 162 """ 163 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) 164 If the value is given and has a unit, then this unit is converted to the unit of the card (mV or %) 165 166 Parameters 167 ---------- 168 value : int | pint.Quantity = None 169 The offset of the specific channel as integer in % or as a Quantity in % or mV 170 unit : pint.Unit = None 171 The unit of the return value 172 173 Returns 174 ------- 175 int | pint.Quantity 176 The offset of the specific channel in % or the unit specified by return_unit 177 """ 178 179 # 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) 180 card_unit = 1 181 fnc_type = self.card.function_type() 182 if fnc_type == SPCM_TYPE_AI: 183 card_unit = units.percent 184 elif fnc_type == SPCM_TYPE_AO: 185 card_unit = units.mV 186 187 if value is not None: 188 # The user gives a value as a Quantity 189 if isinstance(value, pint.Quantity): 190 if fnc_type == SPCM_TYPE_AO: 191 # The card expects a value in mV 192 if value.check('[]'): 193 # Convert from percent to mV 194 value = (value * self._conversion_amp).to(card_unit) 195 else: 196 value = value.to(card_unit) 197 elif fnc_type == SPCM_TYPE_AI: 198 # The card expects a value in percent 199 if value.check('[electric_potential]'): 200 # Convert from mV to percent 201 value = (value / self._conversion_amp).to(card_unit) 202 else: 203 value = value.to(card_unit) 204 else: 205 # Value is given as a number 206 pass 207 208 value = UnitConversion.convert(value, card_unit, int) 209 self.card.set_i(SPC_OFFS0 + (SPC_OFFS1 - SPC_OFFS0) * self.index, value) 210 211 return_value = self.card.get_i(SPC_OFFS0 + (SPC_OFFS1 - SPC_OFFS0) * self.index) 212 # Turn the return value into a quantity 213 return_quantity = UnitConversion.to_unit(return_value, return_unit) 214 # Save the conversion offset to be able to convert the data to a quantity with the correct unit 215 self._conversion_offset = UnitConversion.force_unit(return_value, card_unit) 216 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
218 def convert_data(self, data : npt.NDArray, return_unit : pint.Unit = units.mV, averages : int = 1) -> npt.NDArray: 219 """ 220 Converts the data to the correct unit in units of electrical potential 221 222 Parameters 223 ---------- 224 data : numpy.ndarray 225 The data to be converted 226 return_unit : pint.Unit = units.mV 227 The unit of the return value 228 averages : int = 1 229 The number of averages that have been done to the data and should be taken into account to convert the data 230 231 Returns 232 ------- 233 numpy.ndarray 234 The converted data in units of electrical potential 235 """ 236 237 max_value = self.card.max_sample_value() * averages 238 if self._conversion_offset.check('[]'): 239 return_data = (data / max_value - self._conversion_offset) * self._conversion_amp 240 else: 241 return_data = (data / max_value) * self._conversion_amp - self._conversion_offset 242 return_data = UnitConversion.to_unit(return_data, return_unit) 243 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
245 def reconvert_data(self, data : npt.NDArray) -> npt.NDArray: 246 """ 247 Convert data with units back to integer values in units of electrical potential 248 249 Parameters 250 ---------- 251 data : numpy.ndarray 252 The data to be reconverted 253 254 Returns 255 ------- 256 numpy.ndarray 257 The reconverted data as integer in mV 258 """ 259 260 if self._conversion_offset.check('[]'): 261 return_data = int((data / self._conversion_amp + self._conversion_offset) * self.card.max_sample_value()) 262 else: 263 return_data = int(((data + self._conversion_offset) / self._conversion_amp) * self.card.max_sample_value()) 264 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
266 def termination(self, value : int) -> None: 267 """ 268 Sets the termination of the analog front-end of the channel of the card (see register `SPC_50OHM0` in the manual) 269 270 Parameters 271 ---------- 272 value : int | bool 273 The termination of the specific channel 274 """ 275 276 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
278 def get_termination(self) -> int: 279 """ 280 Gets the termination of the analog front-end of the channel of the card (see register `SPC_50OHM0` in the manual) 281 282 Returns 283 ------- 284 int 285 The termination of the specific channel 286 """ 287 288 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
290 def coupling(self, value : int = None) -> int: 291 """ 292 Sets the coupling of the analog front-end of the channel of the card (see register `SPC_ACDC0` in the manual) 293 294 Parameters 295 ---------- 296 value : int 297 The coupling of the specific channel 298 299 Returns 300 ------- 301 int 302 The coupling of the specific channel 303 """ 304 305 if value is not None: 306 self.card.set_i(SPC_ACDC0 + (SPC_ACDC1 - SPC_ACDC0) * self.index, value) 307 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
309 def coupling_offset_compensation(self, value : int = None) -> int: 310 """ 311 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) 312 313 Parameters 314 ---------- 315 value : int 316 Enables the coupling offset compensation of the specific channel 317 318 Returns 319 ------- 320 int 321 return if the coupling offset compensation of the specific channel is enabled ("1") or disabled ("0") 322 """ 323 324 if value is not None: 325 self.card.set_i(SPC_ACDC_OFFS_COMPENSATION0 + (SPC_ACDC_OFFS_COMPENSATION1 - SPC_ACDC_OFFS_COMPENSATION0) * self.index, value) 326 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")
328 def filter(self, value : int = None) -> int: 329 """ 330 Sets the filter of the analog front-end of the channel of the card (see register `SPC_FILTER0` in the manual) 331 332 Parameters 333 ---------- 334 value : int 335 The filter of the specific channel 336 337 Returns 338 ------- 339 int 340 The filter of the specific channel 341 """ 342 343 if value is not None: 344 self.card.set_i(SPC_FILTER0 + (SPC_FILTER1 - SPC_FILTER0) * self.index, value) 345 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
347 def stop_level(self, value : int = None) -> int: 348 """ 349 Usually the used outputs of the analog generation boards are set to zero level after replay. 350 This is in most cases adequate. In some cases it can be necessary to hold the last sample, 351 to output the maximum positive level or maximum negative level after replay. The stoplevel will 352 stay on the defined level until the next output has been made. With this function 353 you can define the behavior after replay (see register `SPC_CH0_STOPLEVEL` in the manual) 354 355 Parameters 356 ---------- 357 value : int 358 The wanted stop behaviour 359 360 Returns 361 ------- 362 int 363 The stop behaviour of the specific channel 364 """ 365 366 if value is not None: 367 self.card.set_i(SPC_CH0_STOPLEVEL + self.index * (SPC_CH1_STOPLEVEL - SPC_CH0_STOPLEVEL), value) 368 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
370 def custom_stop(self, value : int = None) -> int: 371 """ 372 Allows to define a 16bit wide custom level per channel for the analog output to enter in pauses. The sample format is 373 exactly the same as during replay, as described in the „sample format“ section. 374 When synchronous digital bits are replayed along, the custom level must include these as well and therefore allows to 375 set a custom level for each multi-purpose line separately. (see register `SPC_CH0_CUSTOM_STOP` in the manual) 376 377 Parameters 378 ---------- 379 value : int 380 The custom stop value 381 382 Returns 383 ------- 384 int 385 The custom stop value of the specific channel 386 387 TODO: change this to a specific unit? 388 """ 389 390 if value is not None: 391 self.card.set_i(SPC_CH0_CUSTOM_STOP + self.index * (SPC_CH1_CUSTOM_STOP - SPC_CH0_CUSTOM_STOP), value) 392 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?):
394 def ch_mask(self) -> int: 395 """ 396 Gets mask for the "or"- or "and"-mask 397 398 Returns 399 ------- 400 int 401 The mask for the "or"- or "and"-mask 402 """ 403 404 return 1 << self.index
Gets mask for the "or"- or "and"-mask
Returns
- int: The mask for the "or"- or "and"-mask
406 def output_load(self, value : pint.Quantity = None) -> pint.Quantity: 407 """ 408 Sets the electrical load of the user system connect the channel of the card. This is important for the correct 409 calculation of the output power. Typically, the load would be 50 Ohms, but it can be different. 410 411 Parameters 412 ---------- 413 value : pint.Quantity 414 The electrical load connected by the user to the specific channel 415 416 Returns 417 ------- 418 pint.Quantity 419 The electrical load connected by the user to the specific channel 420 """ 421 if value is not None: 422 self._output_load = value 423 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
425 def voltage_conversion(self, value : pint.Quantity) -> pint.Quantity: 426 """ 427 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 428 429 Parameters 430 ---------- 431 value : pint.Quantity 432 The voltage that is needed at a certain output load 433 434 Returns 435 ------- 436 pint.Quantity 437 The corresponding voltage at an output load of 50 Ohm 438 """ 439 440 # The two at the end is because the value expected by the card is defined for a 50 Ohm load 441 if self._output_load == np.inf * units.ohm: 442 return value / 2 443 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
445 def to_amplitude_fraction(self, value) -> float: 446 """ 447 Convert the voltage, percentage or power to percentage of the full range of the card 448 449 Parameters 450 ---------- 451 value : pint.Quantity | float 452 The voltage that should be outputted at a certain output load 453 454 Returns 455 ------- 456 float 457 The corresponding fraction of the full range of the card 458 """ 459 460 if isinstance(value, units.Quantity) and value.check("[power]"): 461 # U_pk = U_rms * sqrt(2) 462 value = np.sqrt(2 * value.to('mW') * self._output_load) / self._conversion_amp * 100 * units.percent 463 elif isinstance(value, units.Quantity) and value.check("[electric_potential]"): 464 # value in U_pk 465 value = self.voltage_conversion(value) / self._conversion_amp * 100 * units.percent 466 value = UnitConversion.convert(value, units.fraction, float, rounding=None) 467 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
469 def from_amplitude_fraction(self, fraction, return_unit : pint.Quantity = None) -> pint.Quantity: 470 """ 471 Convert the percentage of the full range to voltage, percentage or power 472 473 Parameters 474 ---------- 475 fraction : float 476 The percentage of the full range of the card 477 return_unit : pint.Quantity 478 The unit of the return value 479 480 Returns 481 ------- 482 pint.Quantity 483 The corresponding voltage, percentage or power 484 """ 485 486 return_value = fraction 487 if isinstance(return_unit, units.Unit) and (1*return_unit).check("[power]"): 488 return_value = (np.power(self._conversion_amp * fraction, 2) / self._output_load / 2).to(return_unit) 489 # U_pk = U_rms * sqrt(2) 490 elif isinstance(return_unit, units.Unit) and (1*return_unit).check("[electric_potential]"): 491 return_value = (self._conversion_amp * fraction / (100 * units.percent)).to(return_unit) 492 # value in U_pk 493 # value = self.voltage_conversion(value) / self._conversion_amp * 100 * units.percent 494 elif isinstance(return_unit, units.Unit) and (1*return_unit).check("[]"): 495 return_value = UnitConversion.force_unit(fraction, return_unit) 496 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 max_sample_rate(self, return_unit = None) -> int: 54 """ 55 Returns the maximum sample rate of the active card (see register `SPC_MIINST_MAXADCLOCK` in the manual) 56 57 Returns 58 ------- 59 int 60 """ 61 62 max_sr = self.card.get_i(SPC_MIINST_MAXADCLOCK) 63 if return_unit is not None: max_sr = UnitConversion.to_unit(max_sr * units.Hz, return_unit) 64 return max_sr 65 66 def sample_rate(self, sample_rate = 0, max : bool = False, return_unit = None) -> int: 67 """ 68 Sets or gets the current sample rate of the handled card (see register `SPC_SAMPLERATE` in the manual) 69 70 Parameters 71 ---------- 72 sample_rate : int | pint.Quantity = 0 73 if the parameter sample_rate is given with the function call, then the card's sample rate is set to that value 74 max : bool = False 75 if max is True, the method sets the maximum sample rate of the card 76 unit : pint.Unit = None 77 the unit of the sample rate, by default None 78 79 Returns 80 ------- 81 int 82 the current sample rate in Samples/s 83 """ 84 85 if max: sample_rate = self.max_sample_rate() 86 if sample_rate: 87 if isinstance(sample_rate, units.Quantity) and sample_rate.check("[]"): 88 max_sr = self.max_sample_rate() 89 sample_rate = sample_rate.to_base_units().magnitude * max_sr 90 sample_rate = UnitConversion.convert(sample_rate, units.Hz, int) 91 self.card.set_i(SPC_SAMPLERATE, int(sample_rate)) 92 return_value = self.card.get_i(SPC_SAMPLERATE) 93 if return_unit is not None: return_value = UnitConversion.to_unit(return_value * units.Hz, return_unit) 94 return return_value 95 96 def oversampling_factor(self) -> int: 97 """ 98 Returns the oversampling factor of the card (see register `SPC_OVERSAMPLINGFACTOR` in the manual) 99 100 Returns 101 ------- 102 int 103 the oversampling factor of the card 104 """ 105 106 return self.card.get_i(SPC_OVERSAMPLINGFACTOR) 107 108 def clock_output(self, clock_output : int = None) -> int: 109 """ 110 Set the clock output of the card (see register `SPC_CLOCKOUT` in the manual) 111 112 Parameters 113 ---------- 114 clock_output : int 115 the clock output of the card 116 117 Returns 118 ------- 119 int 120 the clock output of the card 121 """ 122 123 if clock_output is not None: 124 self.card.set_i(SPC_CLOCKOUT, int(clock_output)) 125 return self.card.get_i(SPC_CLOCKOUT) 126 output = clock_output 127 128 def clock_output_frequency(self, return_unit = None) -> int: 129 """ 130 Returns the clock output frequency of the card (see register `SPC_CLOCKOUTFREQUENCY` in the manual) 131 132 Parameters 133 ---------- 134 return_unit : pint.Unit = None 135 the unit of the clock output frequency 136 137 Returns 138 ------- 139 int | pint.Quantity 140 the clock output frequency of the card 141 """ 142 143 value = self.card.get_i(SPC_CLOCKOUTFREQUENCY) 144 value = UnitConversion.to_unit(value * units.Hz, return_unit) 145 return value 146 147 def reference_clock(self, reference_clock : int = None) -> int: 148 """ 149 Set the reference clock of the card (see register `SPC_REFERENCECLOCK` in the manual) 150 151 Parameters 152 ---------- 153 reference_clock : int | pint.Quantity 154 the reference clock of the card in Hz 155 156 Returns 157 ------- 158 int 159 the reference clock of the card in Hz 160 """ 161 162 if reference_clock is not None: 163 reference_clock = UnitConversion.convert(reference_clock, units.Hz, int) 164 self.card.set_i(SPC_REFERENCECLOCK, reference_clock) 165 return self.card.get_i(SPC_REFERENCECLOCK) 166 167 def termination(self, termination : int = None) -> int: 168 """ 169 Set the termination for the clock input of the card (see register `SPC_CLOCK50OHM` in the manual) 170 171 Parameters 172 ---------- 173 termination : int | bool 174 the termination of the card 175 176 Returns 177 ------- 178 int 179 the termination of the card 180 """ 181 182 if termination is not None: 183 self.card.set_i(SPC_CLOCK50OHM, int(termination)) 184 return self.card.get_i(SPC_CLOCK50OHM) 185 186 def threshold(self, value : int = None, return_unit = None) -> int: 187 """ 188 Set the clock threshold of the card (see register `SPC_CLOCKTHRESHOLD` in the manual) 189 190 Parameters 191 ---------- 192 value : int 193 the clock threshold of the card 194 return_unit : pint.Unit = None 195 the unit of the clock threshold 196 197 Returns 198 ------- 199 int | pint.Quantity 200 the clock threshold of the card 201 """ 202 203 if value is not None: 204 value = UnitConversion.convert(value, units.mV, int) 205 self.card.set_i(SPC_CLOCK_THRESHOLD, int(value)) 206 value = self.card.get_i(SPC_CLOCK_THRESHOLD) 207 value = UnitConversion.to_unit(value * units.mV, return_unit) 208 return value 209 210 def threshold_min(self, return_unit = None) -> int: 211 """ 212 Returns the minimum clock threshold of the card (see register `SPC_CLOCK_AVAILTHRESHOLD_MIN` in the manual) 213 214 Parameters 215 ---------- 216 return_unit : pint.Unit = None 217 the unit of the return clock threshold 218 219 Returns 220 ------- 221 int 222 the minimum clock threshold of the card 223 """ 224 225 value = self.card.get_i(SPC_CLOCK_AVAILTHRESHOLD_MIN) 226 value = UnitConversion.to_unit(value * units.mV, return_unit) 227 return value 228 229 def threshold_max(self, return_unit = None) -> int: 230 """ 231 Returns the maximum clock threshold of the card (see register `SPC_CLOCK_AVAILTHRESHOLD_MAX` in the manual) 232 233 Parameters 234 ---------- 235 return_unit : pint.Unit = None 236 the unit of the return clock threshold 237 238 Returns 239 ------- 240 int 241 the maximum clock threshold of the card 242 """ 243 244 value = self.card.get_i(SPC_CLOCK_AVAILTHRESHOLD_MAX) 245 value = UnitConversion.to_unit(value * units.mV, return_unit) 246 return value 247 248 def threshold_step(self, return_unit = None) -> int: 249 """ 250 Returns the step of the clock threshold of the card (see register `SPC_CLOCK_AVAILTHRESHOLD_STEP` in the manual) 251 252 Parameters 253 ---------- 254 return_unit : pint.Unit = None 255 the unit of the return clock threshold 256 257 Returns 258 ------- 259 int 260 the step of the clock threshold of the card 261 """ 262 263 value = self.card.get_i(SPC_CLOCK_AVAILTHRESHOLD_STEP) 264 value = UnitConversion.to_unit(value * units.mV, return_unit) 265 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 max_sample_rate(self, return_unit = None) -> int: 54 """ 55 Returns the maximum sample rate of the active card (see register `SPC_MIINST_MAXADCLOCK` in the manual) 56 57 Returns 58 ------- 59 int 60 """ 61 62 max_sr = self.card.get_i(SPC_MIINST_MAXADCLOCK) 63 if return_unit is not None: max_sr = UnitConversion.to_unit(max_sr * units.Hz, return_unit) 64 return max_sr
Returns the maximum sample rate of the active card (see register SPC_MIINST_MAXADCLOCK
in the manual)
Returns
- int
66 def sample_rate(self, sample_rate = 0, max : bool = False, return_unit = None) -> int: 67 """ 68 Sets or gets the current sample rate of the handled card (see register `SPC_SAMPLERATE` in the manual) 69 70 Parameters 71 ---------- 72 sample_rate : int | pint.Quantity = 0 73 if the parameter sample_rate is given with the function call, then the card's sample rate is set to that value 74 max : bool = False 75 if max is True, the method sets the maximum sample rate of the card 76 unit : pint.Unit = None 77 the unit of the sample rate, by default None 78 79 Returns 80 ------- 81 int 82 the current sample rate in Samples/s 83 """ 84 85 if max: sample_rate = self.max_sample_rate() 86 if sample_rate: 87 if isinstance(sample_rate, units.Quantity) and sample_rate.check("[]"): 88 max_sr = self.max_sample_rate() 89 sample_rate = sample_rate.to_base_units().magnitude * max_sr 90 sample_rate = UnitConversion.convert(sample_rate, units.Hz, int) 91 self.card.set_i(SPC_SAMPLERATE, int(sample_rate)) 92 return_value = self.card.get_i(SPC_SAMPLERATE) 93 if return_unit is not None: return_value = UnitConversion.to_unit(return_value * units.Hz, return_unit) 94 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
- unit (pint.Unit = None): the unit of the sample rate, by default None
Returns
- int: the current sample rate in Samples/s
96 def oversampling_factor(self) -> int: 97 """ 98 Returns the oversampling factor of the card (see register `SPC_OVERSAMPLINGFACTOR` in the manual) 99 100 Returns 101 ------- 102 int 103 the oversampling factor of the card 104 """ 105 106 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
108 def clock_output(self, clock_output : int = None) -> int: 109 """ 110 Set the clock output of the card (see register `SPC_CLOCKOUT` in the manual) 111 112 Parameters 113 ---------- 114 clock_output : int 115 the clock output of the card 116 117 Returns 118 ------- 119 int 120 the clock output of the card 121 """ 122 123 if clock_output is not None: 124 self.card.set_i(SPC_CLOCKOUT, int(clock_output)) 125 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
108 def clock_output(self, clock_output : int = None) -> int: 109 """ 110 Set the clock output of the card (see register `SPC_CLOCKOUT` in the manual) 111 112 Parameters 113 ---------- 114 clock_output : int 115 the clock output of the card 116 117 Returns 118 ------- 119 int 120 the clock output of the card 121 """ 122 123 if clock_output is not None: 124 self.card.set_i(SPC_CLOCKOUT, int(clock_output)) 125 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
128 def clock_output_frequency(self, return_unit = None) -> int: 129 """ 130 Returns the clock output frequency of the card (see register `SPC_CLOCKOUTFREQUENCY` in the manual) 131 132 Parameters 133 ---------- 134 return_unit : pint.Unit = None 135 the unit of the clock output frequency 136 137 Returns 138 ------- 139 int | pint.Quantity 140 the clock output frequency of the card 141 """ 142 143 value = self.card.get_i(SPC_CLOCKOUTFREQUENCY) 144 value = UnitConversion.to_unit(value * units.Hz, return_unit) 145 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
147 def reference_clock(self, reference_clock : int = None) -> int: 148 """ 149 Set the reference clock of the card (see register `SPC_REFERENCECLOCK` in the manual) 150 151 Parameters 152 ---------- 153 reference_clock : int | pint.Quantity 154 the reference clock of the card in Hz 155 156 Returns 157 ------- 158 int 159 the reference clock of the card in Hz 160 """ 161 162 if reference_clock is not None: 163 reference_clock = UnitConversion.convert(reference_clock, units.Hz, int) 164 self.card.set_i(SPC_REFERENCECLOCK, reference_clock) 165 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
167 def termination(self, termination : int = None) -> int: 168 """ 169 Set the termination for the clock input of the card (see register `SPC_CLOCK50OHM` in the manual) 170 171 Parameters 172 ---------- 173 termination : int | bool 174 the termination of the card 175 176 Returns 177 ------- 178 int 179 the termination of the card 180 """ 181 182 if termination is not None: 183 self.card.set_i(SPC_CLOCK50OHM, int(termination)) 184 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
186 def threshold(self, value : int = None, return_unit = None) -> int: 187 """ 188 Set the clock threshold of the card (see register `SPC_CLOCKTHRESHOLD` in the manual) 189 190 Parameters 191 ---------- 192 value : int 193 the clock threshold of the card 194 return_unit : pint.Unit = None 195 the unit of the clock threshold 196 197 Returns 198 ------- 199 int | pint.Quantity 200 the clock threshold of the card 201 """ 202 203 if value is not None: 204 value = UnitConversion.convert(value, units.mV, int) 205 self.card.set_i(SPC_CLOCK_THRESHOLD, int(value)) 206 value = self.card.get_i(SPC_CLOCK_THRESHOLD) 207 value = UnitConversion.to_unit(value * units.mV, return_unit) 208 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
210 def threshold_min(self, return_unit = None) -> int: 211 """ 212 Returns the minimum clock threshold of the card (see register `SPC_CLOCK_AVAILTHRESHOLD_MIN` in the manual) 213 214 Parameters 215 ---------- 216 return_unit : pint.Unit = None 217 the unit of the return clock threshold 218 219 Returns 220 ------- 221 int 222 the minimum clock threshold of the card 223 """ 224 225 value = self.card.get_i(SPC_CLOCK_AVAILTHRESHOLD_MIN) 226 value = UnitConversion.to_unit(value * units.mV, return_unit) 227 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
229 def threshold_max(self, return_unit = None) -> int: 230 """ 231 Returns the maximum clock threshold of the card (see register `SPC_CLOCK_AVAILTHRESHOLD_MAX` in the manual) 232 233 Parameters 234 ---------- 235 return_unit : pint.Unit = None 236 the unit of the return clock threshold 237 238 Returns 239 ------- 240 int 241 the maximum clock threshold of the card 242 """ 243 244 value = self.card.get_i(SPC_CLOCK_AVAILTHRESHOLD_MAX) 245 value = UnitConversion.to_unit(value * units.mV, return_unit) 246 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
248 def threshold_step(self, return_unit = None) -> int: 249 """ 250 Returns the step of the clock threshold of the card (see register `SPC_CLOCK_AVAILTHRESHOLD_STEP` in the manual) 251 252 Parameters 253 ---------- 254 return_unit : pint.Unit = None 255 the unit of the return clock threshold 256 257 Returns 258 ------- 259 int 260 the step of the clock threshold of the card 261 """ 262 263 value = self.card.get_i(SPC_CLOCK_AVAILTHRESHOLD_STEP) 264 value = UnitConversion.to_unit(value * units.mV, return_unit) 265 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
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 34 def __str__(self) -> str: 35 """ 36 String representation of the Trigger class 37 38 Returns 39 ------- 40 str 41 String representation of the Trigger class 42 """ 43 44 return f"Trigger(card={self.card})" 45 46 __repr__ = __str__ 47 48 def enable(self) -> None: 49 """Enables the trigger engine (see command 'M2CMD_CARD_ENABLETRIGGER' in chapter `Trigger` in the manual)""" 50 self.card.cmd(M2CMD_CARD_ENABLETRIGGER) 51 52 def disable(self) -> None: 53 """Disables the trigger engine (see command 'M2CMD_CARD_DISABLETRIGGER' in chapter `Trigger` in the manual)""" 54 self.card.cmd(M2CMD_CARD_DISABLETRIGGER) 55 56 def force(self) -> None: 57 """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)""" 58 self.card.cmd(M2CMD_CARD_FORCETRIGGER) 59 60 def write_setup(self) -> None: 61 """Write the trigger setup to the card""" 62 self.card.write_setup() 63 64 # OR Mask 65 def or_mask(self, mask : int = None) -> int: 66 """ 67 Set the OR mask for the trigger input lines (see register 'SPC_TRIG_ORMASK' in chapter `Trigger` in the manual) 68 69 Parameters 70 ---------- 71 mask : int 72 The OR mask for the trigger input lines 73 74 Returns 75 ------- 76 int 77 The OR mask for the trigger input lines 78 """ 79 80 if mask is not None: 81 self.card.set_i(SPC_TRIG_ORMASK, mask) 82 return self.card.get_i(SPC_TRIG_ORMASK) 83 84 # AND Mask 85 def and_mask(self, mask : int = None) -> int: 86 """ 87 Set the AND mask for the trigger input lines (see register 'SPC_TRIG_ANDMASK' in chapter `Trigger` in the manual) 88 89 Parameters 90 ---------- 91 mask : int 92 The AND mask for the trigger input lines 93 94 Returns 95 ------- 96 int 97 The AND mask for the trigger input lines 98 """ 99 100 if mask is not None: 101 self.card.set_i(SPC_TRIG_ANDMASK, mask) 102 return self.card.get_i(SPC_TRIG_ANDMASK) 103 104 # Channel triggering 105 def ch_mode(self, channel, mode : int = None) -> int: 106 """ 107 Set the mode for the trigger input lines (see register 'SPC_TRIG_CH0_MODE' in chapter `Trigger` in the manual) 108 109 Parameters 110 ---------- 111 channel : int | Channel 112 The channel to set the mode for 113 mode : int 114 The mode for the trigger input lines 115 116 Returns 117 ------- 118 int 119 The mode for the trigger input lines 120 121 """ 122 123 channel_index = int(channel) 124 if mode is not None: 125 self.card.set_i(SPC_TRIG_CH0_MODE + channel_index, mode) 126 return self.card.get_i(SPC_TRIG_CH0_MODE + channel_index) 127 128 def ch_level(self, channel : int, level_num : int, level_value = None, return_unit : pint.Unit = None) -> int: 129 """ 130 Set the level for the trigger input lines (see register 'SPC_TRIG_CH0_LEVEL0' in chapter `Trigger` in the manual) 131 132 Parameters 133 ---------- 134 channel : int | Channel 135 The channel to set the level for 136 level_num : int 137 The level 0 or level 1 138 level_value : int | pint.Quantity | None 139 The level for the trigger input lines 140 141 Returns 142 ------- 143 int 144 The level for the trigger input lines 145 """ 146 147 channel_index = int(channel) 148 # if a level value is given in the form of a quantity, convert it to the card's unit as a integer value 149 if isinstance(level_value, units.Quantity): 150 if isinstance(channel, Channel): 151 level_value = channel.reconvert_data(level_value) 152 elif self.channels and isinstance(self.channels[channel_index], Channel): 153 level_value = self.channels[channel_index].reconvert_data(level_value) 154 else: 155 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.") 156 157 if isinstance(level_value, int): 158 self.card.set_i(SPC_TRIG_CH0_LEVEL0 + channel_index + 100 * level_num, level_value) 159 160 return_value = self.card.get_i(SPC_TRIG_CH0_LEVEL0 + channel_index + 100 * level_num) 161 # if a return unit is given, convert the value to the given unit if a channel object is available 162 if isinstance(return_unit, pint.Unit): 163 if isinstance(channel, Channel): 164 return_value = channel.convert_data(return_value, return_unit=return_unit) 165 elif self.channels and isinstance(self.channels[channel_index], Channel): 166 return_value = self.channels[channel_index].convert_data(return_value, return_unit=return_unit) 167 else: 168 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.") 169 170 return return_value 171 172 def ch_level0(self, channel : int, level_value = None, return_unit : pint.Unit = None) -> int: 173 """ 174 Set the level 0 for the trigger input lines (see register 'SPC_TRIG_CH0_LEVEL0' in chapter `Trigger` in the manual) 175 176 Parameters 177 ---------- 178 channel : int | Channel 179 The channel to set the level for 180 level_value : int | pint.Quantity | None 181 The level for the trigger input lines 182 183 Returns 184 ------- 185 int 186 The level for the trigger input lines 187 """ 188 189 return self.ch_level(channel, 0, level_value, return_unit) 190 191 def ch_level1(self, channel : int, level_value = None, return_unit : pint.Unit = None) -> int: 192 """ 193 Set the level 1 for the trigger input lines (see register 'SPC_TRIG_CH0_LEVEL1' in chapter `Trigger` in the manual) 194 195 Parameters 196 ---------- 197 channel : int | Channel 198 The channel to set the level for 199 level_value : int | pint.Quantity | None 200 The level for the trigger input lines 201 202 Returns 203 ------- 204 int 205 The level for the trigger input lines 206 """ 207 208 return self.ch_level(channel, 1, level_value, return_unit) 209 210 # Channel OR Mask0 211 def ch_or_mask0(self, mask : int = None) -> int: 212 """ 213 Set the channel OR mask0 for the trigger input lines (see register 'SPC_TRIG_CH_ORMASK0' in chapter `Trigger` in the manual) 214 215 Parameters 216 ---------- 217 mask : int 218 The OR mask for the trigger input lines 219 220 Returns 221 ------- 222 int 223 The OR mask for the trigger input lines 224 """ 225 226 if mask is not None: 227 self.card.set_i(SPC_TRIG_CH_ORMASK0, mask) 228 return self.card.get_i(SPC_TRIG_CH_ORMASK0) 229 230 # Channel AND Mask0 231 def ch_and_mask0(self, mask : int = None) -> int: 232 """ 233 Set the AND mask0 for the trigger input lines (see register 'SPC_TRIG_CH_ANDMASK0' in chapter `Trigger` in the manual) 234 235 Parameters 236 ---------- 237 mask : int 238 The AND mask0 for the trigger input lines 239 240 Returns 241 ------- 242 int 243 The AND mask0 for the trigger input lines 244 """ 245 246 if mask is not None: 247 self.card.set_i(SPC_TRIG_CH_ANDMASK0, mask) 248 return self.card.get_i(SPC_TRIG_CH_ANDMASK0) 249 250 # Delay 251 def delay(self, delay = None, return_unit : pint.Unit = None) -> int: 252 """ 253 Set the delay for the trigger input lines in number of sample clocks (see register 'SPC_TRIG_DELAY' in chapter `Trigger` in the manual) 254 255 Parameters 256 ---------- 257 delay : int | pint.Quantity 258 The delay for the trigger input lines 259 return_unit : pint.Unit 260 The unit to return the value in 261 262 Returns 263 ------- 264 int | pint.Quantity 265 The delay for the trigger input lines 266 267 NOTE 268 ---- 269 different cards have different step sizes for the delay. 270 If a delay with unit is given, this function takes the value, 271 calculates the integer value and rounds to the nearest allowed delay value 272 """ 273 274 sr = self.card.get_i(SPC_SAMPLERATE) * units.Hz 275 if delay is not None: 276 if isinstance(delay, units.Quantity): 277 delay_step = self.avail_delay_step() 278 delay = np.rint(int(delay * sr) / delay_step).astype(np.int64) * delay_step 279 self.card.set_i(SPC_TRIG_DELAY, delay) 280 return_value = self.card.get_i(SPC_TRIG_DELAY) 281 if isinstance(return_unit, pint.Unit): return_value = UnitConversion.to_unit(return_value / sr, return_unit) 282 return return_value 283 284 def avail_delay_max(self) -> int: 285 """ 286 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) 287 288 Returns 289 ------- 290 int 291 The maximum delay for the trigger input lines 292 """ 293 294 return self.card.get_i(SPC_TRIG_AVAILDELAY) 295 296 def avail_delay_step(self) -> int: 297 """ 298 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) 299 300 Returns 301 ------- 302 int 303 The step size for the delay for the trigger input lines 304 """ 305 306 return self.card.get_i(SPC_TRIG_AVAILDELAY_STEP) 307 308 309 def trigger_counter(self) -> int: 310 """ 311 Get the number of trigger events since acquisition start (see register 'SPC_TRIGGERCOUNTER' in chapter `Trigger` in the manual) 312 313 Returns 314 ------- 315 int 316 The trigger counter 317 """ 318 319 return self.card.get_i(SPC_TRIGGERCOUNTER) 320 321 # Main external window trigger (ext0/Trg0) 322 def ext0_mode(self, mode : int = None) -> int: 323 """ 324 Set the mode for the main external window trigger (ext0/Trg0) (see register 'SPC_TRIG_EXT0_MODE' in chapter `Trigger` in the manual) 325 326 Parameters 327 ---------- 328 mode : int 329 The mode for the main external window trigger (ext0/Trg0) 330 331 Returns 332 ------- 333 int 334 The mode for the main external window trigger (ext0/Trg0) 335 """ 336 337 if mode is not None: 338 self.card.set_i(SPC_TRIG_EXT0_MODE, mode) 339 return self.card.get_i(SPC_TRIG_EXT0_MODE) 340 341 # Trigger termination 342 def termination(self, termination : int = None) -> int: 343 """ 344 Set the trigger termination (see register 'SPC_TRIG_TERM' in chapter `Trigger` in the manual) 345 346 Parameters 347 ---------- 348 termination : int 349 The trigger termination: a „1“ sets the 50 Ohm termination for external trigger signals. A „0“ sets the high impedance termination 350 351 Returns 352 ------- 353 int 354 The trigger termination: a „1“ sets the 50 Ohm termination for external trigger signals. A „0“ sets the high impedance termination 355 """ 356 357 if termination is not None: 358 self.card.set_i(SPC_TRIG_TERM, termination) 359 return self.card.get_i(SPC_TRIG_TERM) 360 361 # Trigger input coupling 362 def ext0_coupling(self, coupling : int = None) -> int: 363 """ 364 Set the trigger input coupling (see hardware manual register name 'SPC_TRIG_EXT0_ACDC') 365 366 Parameters 367 ---------- 368 coupling : int 369 The trigger input coupling: COUPLING_DC enables DC coupling, COUPLING_AC enables AC coupling for the external trigger 370 input (AC coupling is the default). 371 372 Returns 373 ------- 374 int 375 The trigger input coupling: COUPLING_DC enables DC coupling, COUPLING_AC enables AC coupling for the external trigger 376 input (AC coupling is the default). 377 """ 378 379 if coupling is not None: 380 self.card.set_i(SPC_TRIG_EXT0_ACDC, coupling) 381 return self.card.get_i(SPC_TRIG_EXT0_ACDC) 382 383 # ext1 trigger mode 384 def ext1_mode(self, mode : int = None) -> int: 385 """ 386 Set the mode for the ext1 trigger (see register 'SPC_TRIG_EXT1_MODE' in chapter `Trigger` in the manual) 387 388 Parameters 389 ---------- 390 mode : int 391 The mode for the ext1 trigger 392 393 Returns 394 ------- 395 int 396 The mode for the ext1 trigger 397 """ 398 399 if mode is not None: 400 self.card.set_i(SPC_TRIG_EXT1_MODE, mode) 401 return self.card.get_i(SPC_TRIG_EXT1_MODE) 402 403 # Trigger level 404 def ext0_level0(self, level = None, return_unit = None) -> int: 405 """ 406 Set the trigger level 0 for the ext0 trigger (see register 'SPC_TRIG_EXT0_LEVEL0' in chapter `Trigger` in the manual) 407 408 Parameters 409 ---------- 410 level : int 411 The trigger level 0 for the ext0 trigger in mV 412 return_unit : pint.Unit 413 The unit to return the value in 414 415 Returns 416 ------- 417 int | pint.Quantity 418 The trigger level 0 for the ext0 trigger in mV or in the specified unit 419 """ 420 421 if level is not None: 422 level = UnitConversion.convert(level, units.mV, int) 423 self.card.set_i(SPC_TRIG_EXT0_LEVEL0, level) 424 return_value = self.card.get_i(SPC_TRIG_EXT0_LEVEL0) 425 return UnitConversion.to_unit(return_value * units.mV, return_unit) 426 427 def ext0_level1(self, level = None, return_unit = None) -> int: 428 """ 429 Set the trigger level 1 for the ext0 trigger (see register 'SPC_TRIG_EXT0_LEVEL1' in chapter `Trigger` in the manual) 430 431 Parameters 432 ---------- 433 level : int 434 The trigger level for the ext0 trigger in mV 435 return_unit : pint.Unit 436 The unit to return the value in 437 438 Returns 439 ------- 440 int | pint.Quantity 441 The trigger level for the ext0 trigger in mV or in the specified unit 442 """ 443 444 if level is not None: 445 level = UnitConversion.convert(level, units.mV, int) 446 self.card.set_i(SPC_TRIG_EXT0_LEVEL1, level) 447 return_value = self.card.get_i(SPC_TRIG_EXT0_LEVEL1) 448 return UnitConversion.to_unit(return_value * units.mV, return_unit) 449 450 def ext1_level0(self, level = None, return_unit = None) -> int: 451 """ 452 Set the trigger level 0 for the ext1 trigger (see register 'SPC_TRIG_EXT1_LEVEL0' in chapter `Trigger` in the manual) 453 454 Parameters 455 ---------- 456 level : int 457 The trigger level 0 for the ext1 trigger in mV 458 return_unit : pint.Unit 459 The unit to return the value in 460 461 Returns 462 ------- 463 int | pint.Quantity 464 The trigger level 0 for the ext1 trigger in mV or in the specified unit 465 """ 466 467 if level is not None: 468 level = UnitConversion.convert(level, units.mV, int) 469 self.card.set_i(SPC_TRIG_EXT1_LEVEL0, level) 470 return_value = self.card.get_i(SPC_TRIG_EXT1_LEVEL0) 471 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)
Constructor of the Trigger class
Parameters
- card (Card): The card to use for the Trigger class
48 def enable(self) -> None: 49 """Enables the trigger engine (see command 'M2CMD_CARD_ENABLETRIGGER' in chapter `Trigger` in the manual)""" 50 self.card.cmd(M2CMD_CARD_ENABLETRIGGER)
Enables the trigger engine (see command 'M2CMD_CARD_ENABLETRIGGER' in chapter Trigger
in the manual)
52 def disable(self) -> None: 53 """Disables the trigger engine (see command 'M2CMD_CARD_DISABLETRIGGER' in chapter `Trigger` in the manual)""" 54 self.card.cmd(M2CMD_CARD_DISABLETRIGGER)
Disables the trigger engine (see command 'M2CMD_CARD_DISABLETRIGGER' in chapter Trigger
in the manual)
56 def force(self) -> None: 57 """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)""" 58 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)
60 def write_setup(self) -> None: 61 """Write the trigger setup to the card""" 62 self.card.write_setup()
Write the trigger setup to the card
65 def or_mask(self, mask : int = None) -> int: 66 """ 67 Set the OR mask for the trigger input lines (see register 'SPC_TRIG_ORMASK' in chapter `Trigger` in the manual) 68 69 Parameters 70 ---------- 71 mask : int 72 The OR mask for the trigger input lines 73 74 Returns 75 ------- 76 int 77 The OR mask for the trigger input lines 78 """ 79 80 if mask is not None: 81 self.card.set_i(SPC_TRIG_ORMASK, mask) 82 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
85 def and_mask(self, mask : int = None) -> int: 86 """ 87 Set the AND mask for the trigger input lines (see register 'SPC_TRIG_ANDMASK' in chapter `Trigger` in the manual) 88 89 Parameters 90 ---------- 91 mask : int 92 The AND mask for the trigger input lines 93 94 Returns 95 ------- 96 int 97 The AND mask for the trigger input lines 98 """ 99 100 if mask is not None: 101 self.card.set_i(SPC_TRIG_ANDMASK, mask) 102 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
105 def ch_mode(self, channel, mode : int = None) -> int: 106 """ 107 Set the mode for the trigger input lines (see register 'SPC_TRIG_CH0_MODE' in chapter `Trigger` in the manual) 108 109 Parameters 110 ---------- 111 channel : int | Channel 112 The channel to set the mode for 113 mode : int 114 The mode for the trigger input lines 115 116 Returns 117 ------- 118 int 119 The mode for the trigger input lines 120 121 """ 122 123 channel_index = int(channel) 124 if mode is not None: 125 self.card.set_i(SPC_TRIG_CH0_MODE + channel_index, mode) 126 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
128 def ch_level(self, channel : int, level_num : int, level_value = None, return_unit : pint.Unit = None) -> int: 129 """ 130 Set the level for the trigger input lines (see register 'SPC_TRIG_CH0_LEVEL0' in chapter `Trigger` in the manual) 131 132 Parameters 133 ---------- 134 channel : int | Channel 135 The channel to set the level for 136 level_num : int 137 The level 0 or level 1 138 level_value : int | pint.Quantity | None 139 The level for the trigger input lines 140 141 Returns 142 ------- 143 int 144 The level for the trigger input lines 145 """ 146 147 channel_index = int(channel) 148 # if a level value is given in the form of a quantity, convert it to the card's unit as a integer value 149 if isinstance(level_value, units.Quantity): 150 if isinstance(channel, Channel): 151 level_value = channel.reconvert_data(level_value) 152 elif self.channels and isinstance(self.channels[channel_index], Channel): 153 level_value = self.channels[channel_index].reconvert_data(level_value) 154 else: 155 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.") 156 157 if isinstance(level_value, int): 158 self.card.set_i(SPC_TRIG_CH0_LEVEL0 + channel_index + 100 * level_num, level_value) 159 160 return_value = self.card.get_i(SPC_TRIG_CH0_LEVEL0 + channel_index + 100 * level_num) 161 # if a return unit is given, convert the value to the given unit if a channel object is available 162 if isinstance(return_unit, pint.Unit): 163 if isinstance(channel, Channel): 164 return_value = channel.convert_data(return_value, return_unit=return_unit) 165 elif self.channels and isinstance(self.channels[channel_index], Channel): 166 return_value = self.channels[channel_index].convert_data(return_value, return_unit=return_unit) 167 else: 168 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.") 169 170 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
172 def ch_level0(self, channel : int, level_value = None, return_unit : pint.Unit = None) -> int: 173 """ 174 Set the level 0 for the trigger input lines (see register 'SPC_TRIG_CH0_LEVEL0' in chapter `Trigger` in the manual) 175 176 Parameters 177 ---------- 178 channel : int | Channel 179 The channel to set the level for 180 level_value : int | pint.Quantity | None 181 The level for the trigger input lines 182 183 Returns 184 ------- 185 int 186 The level for the trigger input lines 187 """ 188 189 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
191 def ch_level1(self, channel : int, level_value = None, return_unit : pint.Unit = None) -> int: 192 """ 193 Set the level 1 for the trigger input lines (see register 'SPC_TRIG_CH0_LEVEL1' in chapter `Trigger` in the manual) 194 195 Parameters 196 ---------- 197 channel : int | Channel 198 The channel to set the level for 199 level_value : int | pint.Quantity | None 200 The level for the trigger input lines 201 202 Returns 203 ------- 204 int 205 The level for the trigger input lines 206 """ 207 208 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_or_mask0(self, mask : int = None) -> int: 212 """ 213 Set the channel OR mask0 for the trigger input lines (see register 'SPC_TRIG_CH_ORMASK0' in chapter `Trigger` in the manual) 214 215 Parameters 216 ---------- 217 mask : int 218 The OR mask for the trigger input lines 219 220 Returns 221 ------- 222 int 223 The OR mask for the trigger input lines 224 """ 225 226 if mask is not None: 227 self.card.set_i(SPC_TRIG_CH_ORMASK0, mask) 228 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
231 def ch_and_mask0(self, mask : int = None) -> int: 232 """ 233 Set the AND mask0 for the trigger input lines (see register 'SPC_TRIG_CH_ANDMASK0' in chapter `Trigger` in the manual) 234 235 Parameters 236 ---------- 237 mask : int 238 The AND mask0 for the trigger input lines 239 240 Returns 241 ------- 242 int 243 The AND mask0 for the trigger input lines 244 """ 245 246 if mask is not None: 247 self.card.set_i(SPC_TRIG_CH_ANDMASK0, mask) 248 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
251 def delay(self, delay = None, return_unit : pint.Unit = None) -> int: 252 """ 253 Set the delay for the trigger input lines in number of sample clocks (see register 'SPC_TRIG_DELAY' in chapter `Trigger` in the manual) 254 255 Parameters 256 ---------- 257 delay : int | pint.Quantity 258 The delay for the trigger input lines 259 return_unit : pint.Unit 260 The unit to return the value in 261 262 Returns 263 ------- 264 int | pint.Quantity 265 The delay for the trigger input lines 266 267 NOTE 268 ---- 269 different cards have different step sizes for the delay. 270 If a delay with unit is given, this function takes the value, 271 calculates the integer value and rounds to the nearest allowed delay value 272 """ 273 274 sr = self.card.get_i(SPC_SAMPLERATE) * units.Hz 275 if delay is not None: 276 if isinstance(delay, units.Quantity): 277 delay_step = self.avail_delay_step() 278 delay = np.rint(int(delay * sr) / delay_step).astype(np.int64) * delay_step 279 self.card.set_i(SPC_TRIG_DELAY, delay) 280 return_value = self.card.get_i(SPC_TRIG_DELAY) 281 if isinstance(return_unit, pint.Unit): return_value = UnitConversion.to_unit(return_value / sr, return_unit) 282 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
284 def avail_delay_max(self) -> int: 285 """ 286 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) 287 288 Returns 289 ------- 290 int 291 The maximum delay for the trigger input lines 292 """ 293 294 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
296 def avail_delay_step(self) -> int: 297 """ 298 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) 299 300 Returns 301 ------- 302 int 303 The step size for the delay for the trigger input lines 304 """ 305 306 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
309 def trigger_counter(self) -> int: 310 """ 311 Get the number of trigger events since acquisition start (see register 'SPC_TRIGGERCOUNTER' in chapter `Trigger` in the manual) 312 313 Returns 314 ------- 315 int 316 The trigger counter 317 """ 318 319 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
322 def ext0_mode(self, mode : int = None) -> int: 323 """ 324 Set the mode for the main external window trigger (ext0/Trg0) (see register 'SPC_TRIG_EXT0_MODE' in chapter `Trigger` in the manual) 325 326 Parameters 327 ---------- 328 mode : int 329 The mode for the main external window trigger (ext0/Trg0) 330 331 Returns 332 ------- 333 int 334 The mode for the main external window trigger (ext0/Trg0) 335 """ 336 337 if mode is not None: 338 self.card.set_i(SPC_TRIG_EXT0_MODE, mode) 339 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)
342 def termination(self, termination : int = None) -> int: 343 """ 344 Set the trigger termination (see register 'SPC_TRIG_TERM' in chapter `Trigger` in the manual) 345 346 Parameters 347 ---------- 348 termination : int 349 The trigger termination: a „1“ sets the 50 Ohm termination for external trigger signals. A „0“ sets the high impedance termination 350 351 Returns 352 ------- 353 int 354 The trigger termination: a „1“ sets the 50 Ohm termination for external trigger signals. A „0“ sets the high impedance termination 355 """ 356 357 if termination is not None: 358 self.card.set_i(SPC_TRIG_TERM, termination) 359 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
362 def ext0_coupling(self, coupling : int = None) -> int: 363 """ 364 Set the trigger input coupling (see hardware manual register name 'SPC_TRIG_EXT0_ACDC') 365 366 Parameters 367 ---------- 368 coupling : int 369 The trigger input coupling: COUPLING_DC enables DC coupling, COUPLING_AC enables AC coupling for the external trigger 370 input (AC coupling is the default). 371 372 Returns 373 ------- 374 int 375 The trigger input coupling: COUPLING_DC enables DC coupling, COUPLING_AC enables AC coupling for the external trigger 376 input (AC coupling is the default). 377 """ 378 379 if coupling is not None: 380 self.card.set_i(SPC_TRIG_EXT0_ACDC, coupling) 381 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).
384 def ext1_mode(self, mode : int = None) -> int: 385 """ 386 Set the mode for the ext1 trigger (see register 'SPC_TRIG_EXT1_MODE' in chapter `Trigger` in the manual) 387 388 Parameters 389 ---------- 390 mode : int 391 The mode for the ext1 trigger 392 393 Returns 394 ------- 395 int 396 The mode for the ext1 trigger 397 """ 398 399 if mode is not None: 400 self.card.set_i(SPC_TRIG_EXT1_MODE, mode) 401 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
404 def ext0_level0(self, level = None, return_unit = None) -> int: 405 """ 406 Set the trigger level 0 for the ext0 trigger (see register 'SPC_TRIG_EXT0_LEVEL0' in chapter `Trigger` in the manual) 407 408 Parameters 409 ---------- 410 level : int 411 The trigger level 0 for the ext0 trigger in mV 412 return_unit : pint.Unit 413 The unit to return the value in 414 415 Returns 416 ------- 417 int | pint.Quantity 418 The trigger level 0 for the ext0 trigger in mV or in the specified unit 419 """ 420 421 if level is not None: 422 level = UnitConversion.convert(level, units.mV, int) 423 self.card.set_i(SPC_TRIG_EXT0_LEVEL0, level) 424 return_value = self.card.get_i(SPC_TRIG_EXT0_LEVEL0) 425 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
427 def ext0_level1(self, level = None, return_unit = None) -> int: 428 """ 429 Set the trigger level 1 for the ext0 trigger (see register 'SPC_TRIG_EXT0_LEVEL1' in chapter `Trigger` in the manual) 430 431 Parameters 432 ---------- 433 level : int 434 The trigger level for the ext0 trigger in mV 435 return_unit : pint.Unit 436 The unit to return the value in 437 438 Returns 439 ------- 440 int | pint.Quantity 441 The trigger level for the ext0 trigger in mV or in the specified unit 442 """ 443 444 if level is not None: 445 level = UnitConversion.convert(level, units.mV, int) 446 self.card.set_i(SPC_TRIG_EXT0_LEVEL1, level) 447 return_value = self.card.get_i(SPC_TRIG_EXT0_LEVEL1) 448 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
450 def ext1_level0(self, level = None, return_unit = None) -> int: 451 """ 452 Set the trigger level 0 for the ext1 trigger (see register 'SPC_TRIG_EXT1_LEVEL0' in chapter `Trigger` in the manual) 453 454 Parameters 455 ---------- 456 level : int 457 The trigger level 0 for the ext1 trigger in mV 458 return_unit : pint.Unit 459 The unit to return the value in 460 461 Returns 462 ------- 463 int | pint.Quantity 464 The trigger level 0 for the ext1 trigger in mV or in the specified unit 465 """ 466 467 if level is not None: 468 level = UnitConversion.convert(level, units.mV, int) 469 self.card.set_i(SPC_TRIG_EXT1_LEVEL0, level) 470 return_value = self.card.get_i(SPC_TRIG_EXT1_LEVEL0) 471 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 buffer_size : int = 0 55 notify_size : int = 0 56 57 direction : Direction = Direction.Acquisition 58 59 buffer_type : int 60 num_channels : int = 0 61 bytes_per_sample : int = 0 62 bits_per_sample : int = 0 63 64 current_user_pos : int = 0 65 66 _polling = False 67 _polling_timer = 0 68 69 # private 70 _buffer_samples : int = 0 71 _notify_samples : int = 0 72 73 # private 74 _memory_size : int = 0 75 _c_buffer = None # Internal numpy ctypes buffer object 76 _buffer_alignment : int = 4096 77 _np_buffer : npt.NDArray[np.int_] # Internal object on which the getter setter logic is working 78 _bit_buffer : npt.NDArray[np.int_] 79 _8bit_mode : bool = False 80 _12bit_mode : bool = False 81 _pre_trigger : int = 0 82 83 def __init__(self, card, *args, **kwargs) -> None: 84 """ 85 Initialize the DataTransfer object with a card object and additional arguments 86 87 Parameters 88 ---------- 89 card : Card 90 the card object that is used for the data transfer 91 *args : list 92 list of additional arguments 93 **kwargs : dict 94 dictionary of additional keyword arguments 95 """ 96 97 self.buffer_size = 0 98 self.notify_size = 0 99 self.num_channels = 0 100 self.bytes_per_sample = 0 101 self.bits_per_sample = 0 102 103 self.current_user_pos = 0 104 105 self._buffer_samples = 0 106 self._notify_samples = 0 107 self._memory_size = 0 108 self._c_buffer = None 109 self._buffer_alignment = 4096 110 self._np_buffer = None 111 self._bit_buffer = None 112 self._8bit_mode = False 113 self._12bit_mode = False 114 self._pre_trigger = 0 115 116 super().__init__(card, *args, **kwargs) 117 self.buffer_type = SPCM_BUF_DATA 118 self._bytes_per_sample() 119 self._bits_per_sample() 120 self.num_channels = self.card.active_channels() 121 122 # Find out the direction of transfer 123 if self.function_type == SPCM_TYPE_AI or self.function_type == SPCM_TYPE_DI: 124 self.direction = Direction.Acquisition 125 elif self.function_type == SPCM_TYPE_AO or self.function_type == SPCM_TYPE_DO: 126 self.direction = Direction.Generation 127 else: 128 self.direction = Direction.Undefined 129 130 131 @property 132 def buffer(self) -> npt.NDArray[np.int_]: 133 """ 134 The numpy buffer object that interfaces the Card and can be written and read from 135 136 Returns 137 ------- 138 numpy array 139 the numpy buffer object with the following array index definition: 140 `[channel, sample]` 141 or in case of multiple recording / replay: 142 `[segment, sample, channel]` 143 """ 144 return self._np_buffer 145 146 @buffer.setter 147 def buffer(self, value) -> None: 148 self._np_buffer = value 149 150 @buffer.deleter 151 def buffer(self) -> None: 152 del self._np_buffer 153 154 @property 155 def bit_buffer(self) -> npt.NDArray[np.int_]: 156 """ 157 The bit buffer object that interfaces the Card and can be written and read from 158 159 Returns 160 ------- 161 numpy array 162 with the buffer object where all the individual bits are now unpacked 163 """ 164 165 # self._bit_buffer = self.unpackbits(self._np_buffer) # not a good solution 166 return self._bit_buffer 167 168 @bit_buffer.setter 169 def bit_buffer(self, value) -> None: 170 self._bit_buffer = value 171 172 @bit_buffer.deleter 173 def bit_buffer(self) -> None: 174 del self._bit_buffer 175 176 @property 177 def buffer_samples(self) -> int: 178 """ 179 The number of samples in the buffer 180 181 Returns 182 ------- 183 int 184 the number of samples in the buffer 185 """ 186 return self._buffer_samples 187 188 @buffer_samples.setter 189 def buffer_samples(self, value) -> None: 190 if value is not None: 191 self._buffer_samples = value 192 self.buffer_size = self.samples_to_bytes(self._buffer_samples) 193 194 @buffer_samples.deleter 195 def buffer_samples(self) -> None: 196 del self._buffer_samples 197 198 def _bits_per_sample(self) -> int: 199 """ 200 Get the number of bits per sample 201 202 Returns 203 ------- 204 int 205 number of bits per sample 206 """ 207 if self._8bit_mode: 208 self.bits_per_sample = 8 209 elif self._12bit_mode: 210 self.bits_per_sample = 12 211 else: 212 self.bits_per_sample = self.card.bits_per_sample() 213 return self.bits_per_sample 214 215 def _bytes_per_sample(self) -> int: 216 """ 217 Get the number of bytes per sample 218 219 Returns 220 ------- 221 int 222 number of bytes per sample 223 """ 224 if self._8bit_mode: 225 self.bytes_per_sample = 1 226 elif self._12bit_mode: 227 self.bytes_per_sample = 1.5 228 else: 229 self.bytes_per_sample = self.card.bytes_per_sample() 230 return self.bytes_per_sample 231 232 def bytes_to_samples(self, num_bytes : int) -> int: 233 """ 234 Convert bytes to samples 235 236 Parameters 237 ---------- 238 bytes : int 239 the number of bytes 240 241 Returns 242 ------- 243 int 244 the number of samples 245 """ 246 247 if self.bits_per_sample > 1: 248 num_samples = num_bytes // self.bytes_per_sample // self.num_channels 249 else: 250 num_samples = num_bytes // self.num_channels * 8 251 return num_samples 252 253 def samples_to_bytes(self, num_samples : int) -> int: 254 """ 255 Convert samples to bytes 256 257 Parameters 258 ---------- 259 num_samples : int 260 the number of samples 261 262 Returns 263 ------- 264 int 265 the number of bytes 266 """ 267 268 if self.bits_per_sample > 1: 269 num_bytes = num_samples * self.bytes_per_sample * self.num_channels 270 else: 271 num_bytes = num_samples * self.num_channels // 8 272 return num_bytes 273 274 def notify_samples(self, notify_samples : int = None) -> int: 275 """ 276 Set the number of samples to notify the user about 277 278 Parameters 279 ---------- 280 notify_samples : int | pint.Quantity 281 the number of samples to notify the user about 282 """ 283 284 if notify_samples is not None: 285 notify_samples = UnitConversion.convert(notify_samples, units.Sa, int) 286 self._notify_samples = notify_samples 287 self.notify_size = self.samples_to_bytes(self._notify_samples) 288 return self._notify_samples 289 290 def _sample_rate(self) -> pint.Quantity: 291 """ 292 Get the sample rate of the card 293 294 Returns 295 ------- 296 pint.Quantity 297 the sample rate of the card in Hz as a pint quantity 298 """ 299 return self.card.get_i(SPC_SAMPLERATE) * units.Hz 300 301 def memory_size(self, memory_samples : int = None) -> int: 302 """ 303 Sets the memory size in samples per channel. The memory size setting must be set before transferring 304 data to the card. (see register `SPC_MEMSIZE` in the manual) 305 306 Parameters 307 ---------- 308 memory_samples : int | pint.Quantity 309 the size of the memory in samples 310 311 Returns 312 ------- 313 int 314 the size of the memory in samples 315 """ 316 317 if memory_samples is not None: 318 memory_samples = UnitConversion.convert(memory_samples, units.Sa, int) 319 self.card.set_i(SPC_MEMSIZE, memory_samples) 320 self._memory_size = self.card.get_i(SPC_MEMSIZE) 321 return self._memory_size 322 323 def output_buffer_size(self, buffer_samples : int = None) -> int: 324 """ 325 Set the size of the output buffer (see register `SPC_DATA_OUTBUFSIZE` in the manual) 326 327 Parameters 328 ---------- 329 buffer_samples : int | pint.Quantity 330 the size of the output buffer in Bytes 331 332 Returns 333 ------- 334 int 335 the size of the output buffer in Samples 336 """ 337 338 if buffer_samples is not None: 339 buffer_samples = UnitConversion.convert(buffer_samples, units.B, int) 340 buffer_size = self.samples_to_bytes(buffer_samples) 341 self.card.set_i(SPC_DATA_OUTBUFSIZE, buffer_size) 342 return self.bytes_to_samples(self.card.get_i(SPC_DATA_OUTBUFSIZE)) 343 344 def loops(self, loops : int = None) -> int: 345 return self.card.loops(loops) 346 347 def pre_trigger(self, num_samples : int = None) -> int: 348 """ 349 Set the number of pre trigger samples (see register `SPC_PRETRIGGER` in the manual) 350 351 Parameters 352 ---------- 353 num_samples : int | pint.Quantity 354 the number of pre trigger samples 355 356 Returns 357 ------- 358 int 359 the number of pre trigger samples 360 """ 361 362 if num_samples is not None: 363 num_samples = UnitConversion.convert(num_samples, units.Sa, int) 364 self.card.set_i(SPC_PRETRIGGER, num_samples) 365 self._pre_trigger = self.card.get_i(SPC_PRETRIGGER) 366 return self._pre_trigger 367 368 def post_trigger(self, num_samples : int = None) -> int: 369 """ 370 Set the number of post trigger samples (see register `SPC_POSTTRIGGER` in the manual) 371 372 Parameters 373 ---------- 374 num_samples : int | pint.Quantity 375 the number of post trigger samples 376 377 Returns 378 ------- 379 int 380 the number of post trigger samples 381 """ 382 383 if self._memory_size < num_samples: 384 raise ValueError(f"The number of post trigger samples needs to be smaller than the total number of samples: {self._memory_size} < {num_samples}") 385 if num_samples is not None: 386 num_samples = UnitConversion.convert(num_samples, units.Sa, int) 387 self.card.set_i(SPC_POSTTRIGGER, num_samples) 388 post_trigger = self.card.get_i(SPC_POSTTRIGGER) 389 self._pre_trigger = self._memory_size - post_trigger 390 return post_trigger 391 392 def allocate_buffer(self, num_samples : int, no_reshape = False) -> None: 393 """ 394 Memory allocation for the buffer that is used for communicating with the card 395 396 Parameters 397 ---------- 398 num_samples : int | pint.Quantity = None 399 use the number of samples an get the number of active channels and bytes per samples directly from the card 400 """ 401 402 self.buffer_samples = UnitConversion.convert(num_samples, units.Sa, int) 403 404 sample_type = self.numpy_type() 405 406 dwMask = self._buffer_alignment - 1 407 408 item_size = sample_type(0).itemsize 409 # print(f"Item size: {item_size}") 410 # allocate a buffer (numpy array) for DMA transfer: a little bigger one to have room for address alignment 411 databuffer_unaligned = np.empty(((self._buffer_alignment + self.buffer_size) // item_size, ), dtype = sample_type) # byte count to sample (// = integer division) 412 # two numpy-arrays may share the same memory: skip the begin up to the alignment boundary (ArrayVariable[SKIP_VALUE:]) 413 # Address of data-memory from numpy-array: ArrayVariable.__array_interface__['data'][0] 414 start_pos_samples = ((self._buffer_alignment - (databuffer_unaligned.__array_interface__['data'][0] & dwMask)) // item_size) 415 self.buffer = databuffer_unaligned[start_pos_samples:start_pos_samples + (self.buffer_size // item_size)] # byte address to sample size 416 if self.bits_per_sample == 1: 417 self.unpackbits() # allocate the bit buffer for digital cards 418 elif not self._12bit_mode and not no_reshape: 419 self.buffer = self.buffer.reshape((self.num_channels, self.buffer_samples), order='F') # index definition: [channel, sample] ! 420 421 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: 422 """ 423 Start the transfer of the data to or from the card (see the API function `spcm_dwDefTransfer_i64` in the manual) 424 425 Parameters 426 ---------- 427 *args : list 428 list of additonal arguments that are added as flags to the start dma command 429 buffer_type : int 430 the type of buffer that is used for the transfer 431 direction : int 432 the direction of the transfer 433 notify_samples : int 434 the number of samples to notify the user about 435 transfer_offset : int 436 the offset of the transfer 437 transfer_length : int 438 the length of the transfer 439 exception_num_samples : bool 440 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. 441 442 Raises 443 ------ 444 SpcmException 445 """ 446 447 self.notify_samples(UnitConversion.convert(notify_samples, units.Sa, int)) 448 transfer_offset = UnitConversion.convert(transfer_offset, units.Sa, int) 449 transfer_length = UnitConversion.convert(transfer_length, units.Sa, int) 450 451 if transfer_length is not None: 452 self.buffer_samples = transfer_length 453 454 if self.buffer is None: 455 raise SpcmException(text="No buffer defined for transfer") 456 if buffer_type: 457 self.buffer_type = buffer_type 458 if direction is None: 459 if self.direction == Direction.Acquisition: 460 direction = SPCM_DIR_CARDTOPC 461 elif self.direction == Direction.Generation: 462 direction = SPCM_DIR_PCTOCARD 463 else: 464 raise SpcmException(text="Please define a direction for transfer (SPCM_DIR_CARDTOPC or SPCM_DIR_PCTOCARD)") 465 466 if self._notify_samples != 0 and np.remainder(self.buffer_samples, self._notify_samples) and exception_num_samples: 467 raise SpcmException("The number of samples needs to be a multiple of the notify samples.") 468 469 if transfer_offset: 470 transfer_offset_bytes = self.samples_to_bytes(transfer_offset) 471 else: 472 transfer_offset_bytes = 0 473 474 # we define the buffer for transfer 475 self.card._print("Starting the DMA transfer and waiting until data is in board memory") 476 self._c_buffer = self.buffer.ctypes.data_as(c_void_p) 477 self.card._check_error(spcm_dwDefTransfer_i64(self.card._handle, self.buffer_type, direction, self.notify_size, self._c_buffer, transfer_offset_bytes, self.buffer_size)) 478 479 # Execute additional commands if available 480 if args: 481 cmd = 0 482 for arg in args: 483 cmd |= arg 484 self.card.cmd(cmd) 485 self.card._print("... data transfer started") 486 487 def duration(self, duration : pint.Quantity, pre_trigger_duration : pint.Quantity = None, post_trigger_duration : pint.Quantity = None) -> None: 488 """ 489 Set the duration of the data transfer 490 491 Parameters 492 ---------- 493 duration : pint.Quantity 494 the duration of the data transfer 495 pre_trigger_duration : pint.Quantity = None 496 the duration before the trigger event 497 post_trigger_duration : pint.Quantity = None 498 the duration after the trigger event 499 500 Returns 501 ------- 502 pint.Quantity 503 the duration of the data transfer 504 """ 505 506 if pre_trigger_duration is None and post_trigger_duration is None: 507 raise ValueError("Please define either pre_trigger_duration or post_trigger_duration") 508 509 memsize_min = self.card.get_i(SPC_AVAILMEMSIZE_MIN) 510 memsize_max = self.card.get_i(SPC_AVAILMEMSIZE_MAX) 511 memsize_stp = self.card.get_i(SPC_AVAILMEMSIZE_STEP) 512 num_samples = (duration * self._sample_rate()).to_base_units().magnitude 513 num_samples = np.ceil(num_samples / memsize_stp) * memsize_stp 514 num_samples = np.clip(num_samples, memsize_min, memsize_max) 515 num_samples = int(num_samples) 516 self.memory_size(num_samples) 517 self.allocate_buffer(num_samples) 518 if pre_trigger_duration is not None: 519 pre_min = self.card.get_i(SPC_AVAILPRETRIGGER_MIN) 520 pre_max = self.card.get_i(SPC_AVAILPRETRIGGER_MAX) 521 pre_stp = self.card.get_i(SPC_AVAILPRETRIGGER_STEP) 522 pre_samples = (pre_trigger_duration * self._sample_rate()).to_base_units().magnitude 523 pre_samples = np.ceil(pre_samples / pre_stp) * pre_stp 524 pre_samples = np.clip(pre_samples, pre_min, pre_max) 525 pre_samples = int(post_samples) 526 self.post_trigger(post_samples) 527 if post_trigger_duration is not None: 528 post_min = self.card.get_i(SPC_AVAILPOSTTRIGGER_MIN) 529 post_max = self.card.get_i(SPC_AVAILPOSTTRIGGER_MAX) 530 post_stp = self.card.get_i(SPC_AVAILPOSTTRIGGER_STEP) 531 post_samples = (post_trigger_duration * self._sample_rate()).to_base_units().magnitude 532 post_samples = np.ceil(post_samples / post_stp) * post_stp 533 post_samples = np.clip(post_samples, post_min, post_max) 534 post_samples = int(post_samples) 535 self.post_trigger(post_samples) 536 return num_samples, post_samples 537 538 def time_data(self, total_num_samples : int = None, return_units = units.s) -> npt.NDArray: 539 """ 540 Get the time array for the data buffer 541 542 Parameters 543 ---------- 544 total_num_samples : int | pint.Quantity 545 the total number of samples 546 return_units : pint.Quantity 547 the units that the time should be converted to 548 549 Returns 550 ------- 551 numpy array 552 the time array 553 """ 554 555 if total_num_samples is None: 556 total_num_samples = self._buffer_samples 557 total_num_samples = UnitConversion.convert(total_num_samples, units.Sa, int) 558 pre_trigger = UnitConversion.convert(self._pre_trigger, units.Sa, int) 559 return self.convert_time((np.arange(total_num_samples) - pre_trigger)).to(return_units) 560 561 def convert_time(self, time, return_units = units.s): 562 """ 563 Convert a time to the units of the card sample rate 564 565 Parameters 566 ---------- 567 time : numpy array 568 the time array with integers that should be converted 569 return_units : numpy array with pint.Quantity 570 the units that the time should be converted to 571 572 Returns 573 ------- 574 pint.Quantity 575 the converted time 576 """ 577 578 sample_rate = self._sample_rate() 579 return (time / sample_rate).to(return_units) 580 581 def unpack_12bit_buffer(self, data : npt.NDArray[np.int_] = None) -> npt.NDArray[np.int_]: 582 """ 583 Unpacks the 12-bit packed data to 16-bit data 584 585 Parameters 586 ---------- 587 data : numpy array 588 the packed data 589 590 Returns 591 ------- 592 numpy array 593 the unpacked 16bit buffer 594 """ 595 596 if not self._12bit_mode: 597 raise SpcmException("The card is not in 12bit packed mode") 598 599 if data is None: 600 data = self.buffer 601 602 fst_int8, mid_int8, lst_int8 = np.reshape(data, (data.shape[0] // 3, 3)).astype(np.int16).T 603 nibble_h = (mid_int8 >> 0) & 0x0F 604 nibble_m = (fst_int8 >> 4) & 0x0F 605 nibble_l = (fst_int8 >> 0) & 0x0F 606 fst_int12 = ((nibble_h << 12) >> 4) | (nibble_m << 4) | (nibble_l << 0) 607 nibble_h = (lst_int8 >> 4) & 0x0F 608 nibble_m = (lst_int8 >> 0) & 0x0F 609 nibble_l = (mid_int8 >> 4) & 0x0F 610 snd_int12 = ((nibble_h << 12) >> 4) | (nibble_m << 4) | (nibble_l << 0) 611 data_int12 = np.concatenate((fst_int12[:, None], snd_int12[:, None]), axis=1).reshape((-1,)) 612 data_int12 = data_int12.reshape((self.num_channels, self._buffer_samples), order='F') 613 return data_int12 614 615 def unpackbits(self, data : npt.NDArray[np.int_] = None) -> npt.NDArray[np.int_]: 616 """ 617 Unpack the buffer to bits 618 619 Parameters 620 ---------- 621 data : numpy array | None = None 622 the packed data 623 624 Returns 625 ------- 626 numpy array 627 the unpacked buffer 628 """ 629 630 if data is None: 631 data = self.buffer 632 dshape = list(data.shape) 633 return_data = data.reshape([-1, 1]) 634 num_bits = return_data.dtype.itemsize * 8 635 mask = 2**np.arange(num_bits, dtype=return_data.dtype).reshape([1, num_bits]) 636 self.bit_buffer = (return_data & mask).astype(np.bool).astype(np.uint8).reshape(dshape + [num_bits]) 637 return self.bit_buffer 638 639 def packbits(self) -> None: 640 """ 641 Pack the self.buffer from the self.bit_buffer 642 """ 643 644 self.buffer[:] = np.packbits(self._bit_buffer, axis=-1, bitorder='little').view(self.buffer.dtype).reshape(self.buffer.shape) 645 646 def tofile(self, filename : str, buffer = None, **kwargs) -> None: 647 """ 648 Export the buffer to a file. The file format is determined by the file extension 649 Supported file formats are: 650 * .bin: raw binary file 651 * .csv: comma-separated values file 652 * .npy: numpy binary file 653 * .npz: compressed numpy binary file 654 * .txt: whitespace-delimited text file 655 * .h5: hdf5 file format 656 657 Parameters 658 ---------- 659 filename : str 660 the name of the file that the buffer should be exported to 661 662 Raises 663 ------ 664 ImportError 665 if the file format is not supported 666 """ 667 668 if buffer is None: 669 buffer = self.buffer 670 file_path = Path(filename) 671 if file_path.suffix == '.bin': 672 buffer.tofile(file_path) 673 elif file_path.suffix == '.csv': 674 delimiter = kwargs.get('delimiter', ',') 675 np.savetxt(file_path, buffer, delimiter=delimiter) 676 elif file_path.suffix == '.npy': 677 np.save(file_path, buffer) 678 elif file_path.suffix == '.npz': 679 np.savez_compressed(file_path, buffer) 680 elif file_path.suffix == '.txt': 681 np.savetxt(file_path, buffer, fmt='%d') 682 elif file_path.suffix == '.h5' or file_path.suffix == '.hdf5': 683 import h5py 684 with h5py.File(file_path, 'w') as f: 685 f.create_dataset('data', data=buffer) 686 else: 687 raise ImportError("File format not supported") 688 689 def fromfile(self, filename : str, in_buffer : bool = True, **kwargs) -> npt.NDArray[np.int_]: 690 """ 691 Import the buffer from a file. The file format is determined by the file extension 692 Supported file formats are: 693 * .bin: raw binary file 694 * .csv: comma-separated values file 695 * .npy: numpy binary file 696 * .npz: compressed numpy binary file 697 * .txt: whitespace-delimited text file 698 * .h5: hdf5 file format 699 700 Parameters 701 ---------- 702 filename : str 703 the name of the file that the buffer should be imported from 704 705 Raises 706 ------ 707 ImportError 708 if the file format is not supported 709 """ 710 711 file_path = Path(filename) 712 if file_path.suffix == '.bin': 713 dtype = kwargs.get('dtype', self.numpy_type()) 714 shape = kwargs.get('shape', (self.num_channels, self.buffer_size // self.num_channels)) 715 buffer = np.fromfile(file_path, dtype=dtype) 716 loaded_data = buffer.reshape(shape, order='C') 717 elif file_path.suffix == '.csv': 718 delimiter = kwargs.get('delimiter', ',') 719 loaded_data = np.loadtxt(file_path, delimiter=delimiter) 720 elif file_path.suffix == '.npy': 721 loaded_data = np.load(file_path) 722 elif file_path.suffix == '.npz': 723 data = np.load(file_path) 724 loaded_data = data['arr_0'] 725 elif file_path.suffix == '.txt': 726 loaded_data = np.loadtxt(file_path) 727 elif file_path.suffix == '.h5' or file_path.suffix == '.hdf5': 728 import h5py 729 with h5py.File(file_path, 'r') as f: 730 loaded_data = f['data'][()] 731 else: 732 raise ImportError("File format not supported") 733 734 if in_buffer: 735 self.buffer[:] = loaded_data 736 return loaded_data 737 738 739 def avail_card_len(self, available_samples : int = 0) -> None: 740 """ 741 Set the amount of data that has been read out of the data buffer (see register `SPC_DATA_AVAIL_CARD_LEN` in the manual) 742 743 Parameters 744 ---------- 745 available_samples : int | pint.Quantity 746 the amount of data that is available for reading 747 """ 748 749 available_samples = UnitConversion.convert(available_samples, units.Sa, int) 750 # print(available_samples, self.bytes_per_sample, self.num_channels) 751 available_bytes = self.samples_to_bytes(available_samples) 752 self.card.set_i(SPC_DATA_AVAIL_CARD_LEN, available_bytes) 753 754 def avail_user_pos(self, in_bytes : bool = False) -> int: 755 """ 756 Get the current position of the pointer in the data buffer (see register `SPC_DATA_AVAIL_USER_POS` in the manual) 757 758 Parameters 759 ---------- 760 in_bytes : bool 761 if True, the position is returned in bytes 762 763 Returns 764 ------- 765 int 766 pointer position 767 """ 768 769 self.current_user_pos = self.card.get_i(SPC_DATA_AVAIL_USER_POS) 770 if not in_bytes: 771 self.current_user_pos = self.bytes_to_samples(self.current_user_pos) 772 return self.current_user_pos 773 774 def avail_user_len(self, in_bytes : bool = False) -> int: 775 """ 776 Get the current length of the data in the data buffer (see register `SPC_DATA_AVAIL_USER_LEN` in the manual) 777 778 Parameters 779 ---------- 780 in_bytes : bool 781 if True, the length is returned in bytes 782 783 Returns 784 ------- 785 int 786 data length available 787 """ 788 789 user_len = self.card.get_i(SPC_DATA_AVAIL_USER_LEN) 790 if not in_bytes: 791 user_len = self.bytes_to_samples(user_len) 792 return user_len 793 794 def fill_size_promille(self, return_unit = None) -> int: 795 """ 796 Get the fill size of the data buffer (see register `SPC_FILLSIZEPROMILLE` in the manual) 797 798 Returns 799 ------- 800 int 801 fill size 802 """ 803 804 return_value = self.card.get_i(SPC_FILLSIZEPROMILLE) 805 if return_unit is not None: return_value = UnitConversion.to_unit(return_value * units.promille, return_unit) 806 return return_value 807 808 def wait_dma(self) -> None: 809 """ 810 Wait for the DMA transfer to finish (see register `M2CMD_DATA_WAITDMA` in the manual) 811 """ 812 813 self.card.cmd(M2CMD_DATA_WAITDMA) 814 wait = wait_dma 815 816 def numpy_type(self) -> npt.NDArray[np.int_]: 817 """ 818 Get the type of numpy data from number of bytes 819 820 Returns 821 ------- 822 numpy data type 823 the type of data that is used by the card 824 """ 825 826 if self._8bit_mode: 827 return np.uint8 828 if self._12bit_mode: 829 return np.int8 830 if self.bits_per_sample == 1: 831 if self.num_channels <= 8: 832 return np.uint8 833 elif self.num_channels <= 16: 834 return np.uint16 835 elif self.num_channels <= 32: 836 return np.uint32 837 return np.uint64 838 if self.bits_per_sample <= 8: 839 return np.int8 840 elif self.bits_per_sample <= 16: 841 return np.int16 842 elif self.bits_per_sample <= 32: 843 return np.int32 844 return np.int64 845 846 # Data conversion mode 847 def data_conversion(self, mode : int = None) -> int: 848 """ 849 Set the data conversion mode (see register `SPC_DATACONVERSION` in the manual) 850 851 Parameters 852 ---------- 853 mode : int 854 the data conversion mode 855 """ 856 857 if mode is not None: 858 self.card.set_i(SPC_DATACONVERSION, mode) 859 mode = self.card.get_i(SPC_DATACONVERSION) 860 self._8bit_mode = (mode == SPCM_DC_12BIT_TO_8BIT or mode == SPCM_DC_14BIT_TO_8BIT or mode == SPCM_DC_16BIT_TO_8BIT) 861 self._12bit_mode = (mode == SPCM_DC_12BIT_TO_12BITPACKED) 862 self._bits_per_sample() 863 self._bytes_per_sample() 864 return mode 865 866 def avail_data_conversion(self) -> int: 867 """ 868 Get the available data conversion modes (see register `SPC_AVAILDATACONVERSION` in the manual) 869 870 Returns 871 ------- 872 int 873 the available data conversion modes 874 """ 875 return self.card.get_i(SPC_AVAILDATACONVERSION) 876 877 # Iterator methods 878 879 iterator_index = 0 880 _max_timeout = 64 881 882 _to_transfer_samples = 0 883 _current_samples = 0 884 885 _verbose = False 886 887 def verbose(self, verbose : bool = None) -> bool: 888 """ 889 Set or get the verbose mode for the data transfer 890 891 Parameters 892 ---------- 893 verbose : bool = None 894 the verbose mode 895 """ 896 897 if verbose is not None: 898 self._verbose = verbose 899 return self._verbose 900 901 def to_transfer_samples(self, samples) -> None: 902 """ 903 This method sets the number of samples to transfer 904 905 Parameters 906 ---------- 907 samples : int | pint.Quantity 908 the number of samples to transfer 909 """ 910 911 samples = UnitConversion.convert(samples, units.Sa, int) 912 self._to_transfer_samples = samples 913 914 def __iter__(self): 915 """ 916 This method is called when the iterator is initialized 917 918 Returns 919 ------- 920 DataIterator 921 the iterator itself 922 """ 923 924 self.iterator_index = 0 925 return self 926 927 def polling(self, polling : bool = True, timer : float = 0.01) -> None: 928 """ 929 Set the polling mode for the data transfer otherwise wait_dma() is used 930 931 Parameters 932 ---------- 933 polling : bool 934 True to enable polling, False to disable polling 935 timer : float | pint.Quantity 936 the polling timer in seconds 937 """ 938 939 self._polling = polling 940 self._polling_timer = UnitConversion.convert(timer, units.s, float, rounding=None) 941 942 _auto_avail_card_len = True 943 def auto_avail_card_len(self, value : bool = None) -> bool: 944 """ 945 Enable or disable the automatic sending of the number of samples that the card can now use for sample data transfer again 946 947 Parameters 948 ---------- 949 value : bool = None 950 True to enable, False to disable and None to get the current status 951 952 Returns 953 ------- 954 bool 955 the current status 956 """ 957 if value is not None: 958 self._auto_avail_card_len = value 959 return self._auto_avail_card_len 960 961 def __next__(self) -> npt.ArrayLike: 962 """ 963 This method is called when the next element is requested from the iterator 964 965 Returns 966 ------- 967 npt.ArrayLike 968 the next data block 969 970 Raises 971 ------ 972 StopIteration 973 """ 974 timeout_counter = 0 975 # notify the card that data is available or read, but only after the first block 976 if self.iterator_index != 0 and self._auto_avail_card_len: 977 self.flush() 978 979 while True: 980 try: 981 if not self._polling: 982 self.wait_dma() 983 else: 984 user_len = self.avail_user_len() 985 if user_len >= self._notify_samples: 986 break 987 time.sleep(self._polling_timer) 988 except SpcmTimeout: 989 self.card._print("... Timeout ({})".format(timeout_counter), end='\r') 990 timeout_counter += 1 991 if timeout_counter > self._max_timeout: 992 self.iterator_index = 0 993 raise StopIteration 994 else: 995 if not self._polling: 996 break 997 998 self.iterator_index += 1 999 1000 self._current_samples += self._notify_samples 1001 if self._to_transfer_samples != 0 and self._to_transfer_samples < self._current_samples: 1002 self.iterator_index = 0 1003 raise StopIteration 1004 1005 user_pos = self.avail_user_pos() 1006 fill_size = self.fill_size_promille() 1007 1008 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) 1009 1010 return self.buffer[:, user_pos:user_pos+self._notify_samples] 1011 1012 def flush(self): 1013 """ 1014 This method is used to tell the card that a notify size of data is freed up after reading (acquisition) or written to (generation) 1015 """ 1016 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.
83 def __init__(self, card, *args, **kwargs) -> None: 84 """ 85 Initialize the DataTransfer object with a card object and additional arguments 86 87 Parameters 88 ---------- 89 card : Card 90 the card object that is used for the data transfer 91 *args : list 92 list of additional arguments 93 **kwargs : dict 94 dictionary of additional keyword arguments 95 """ 96 97 self.buffer_size = 0 98 self.notify_size = 0 99 self.num_channels = 0 100 self.bytes_per_sample = 0 101 self.bits_per_sample = 0 102 103 self.current_user_pos = 0 104 105 self._buffer_samples = 0 106 self._notify_samples = 0 107 self._memory_size = 0 108 self._c_buffer = None 109 self._buffer_alignment = 4096 110 self._np_buffer = None 111 self._bit_buffer = None 112 self._8bit_mode = False 113 self._12bit_mode = False 114 self._pre_trigger = 0 115 116 super().__init__(card, *args, **kwargs) 117 self.buffer_type = SPCM_BUF_DATA 118 self._bytes_per_sample() 119 self._bits_per_sample() 120 self.num_channels = self.card.active_channels() 121 122 # Find out the direction of transfer 123 if self.function_type == SPCM_TYPE_AI or self.function_type == SPCM_TYPE_DI: 124 self.direction = Direction.Acquisition 125 elif self.function_type == SPCM_TYPE_AO or self.function_type == SPCM_TYPE_DO: 126 self.direction = Direction.Generation 127 else: 128 self.direction = Direction.Undefined
Initialize the DataTransfer object with a card object and additional arguments
Parameters
- card (Card): the card object that is used for the data transfer
- *args (list): list of additional arguments
- **kwargs (dict): dictionary of additional keyword arguments
131 @property 132 def buffer(self) -> npt.NDArray[np.int_]: 133 """ 134 The numpy buffer object that interfaces the Card and can be written and read from 135 136 Returns 137 ------- 138 numpy array 139 the numpy buffer object with the following array index definition: 140 `[channel, sample]` 141 or in case of multiple recording / replay: 142 `[segment, sample, channel]` 143 """ 144 return self._np_buffer
The numpy buffer object that interfaces the Card and can be written and read from
Returns
- numpy array: the numpy buffer object with the following array index definition:
[channel, sample]
or in case of multiple recording / replay:[segment, sample, channel]
154 @property 155 def bit_buffer(self) -> npt.NDArray[np.int_]: 156 """ 157 The bit buffer object that interfaces the Card and can be written and read from 158 159 Returns 160 ------- 161 numpy array 162 with the buffer object where all the individual bits are now unpacked 163 """ 164 165 # self._bit_buffer = self.unpackbits(self._np_buffer) # not a good solution 166 return self._bit_buffer
The bit buffer object that interfaces the Card and can be written and read from
Returns
- numpy array: with the buffer object where all the individual bits are now unpacked
176 @property 177 def buffer_samples(self) -> int: 178 """ 179 The number of samples in the buffer 180 181 Returns 182 ------- 183 int 184 the number of samples in the buffer 185 """ 186 return self._buffer_samples
The number of samples in the buffer
Returns
- int: the number of samples in the buffer
232 def bytes_to_samples(self, num_bytes : int) -> int: 233 """ 234 Convert bytes to samples 235 236 Parameters 237 ---------- 238 bytes : int 239 the number of bytes 240 241 Returns 242 ------- 243 int 244 the number of samples 245 """ 246 247 if self.bits_per_sample > 1: 248 num_samples = num_bytes // self.bytes_per_sample // self.num_channels 249 else: 250 num_samples = num_bytes // self.num_channels * 8 251 return num_samples
Convert bytes to samples
Parameters
- bytes (int): the number of bytes
Returns
- int: the number of samples
253 def samples_to_bytes(self, num_samples : int) -> int: 254 """ 255 Convert samples to bytes 256 257 Parameters 258 ---------- 259 num_samples : int 260 the number of samples 261 262 Returns 263 ------- 264 int 265 the number of bytes 266 """ 267 268 if self.bits_per_sample > 1: 269 num_bytes = num_samples * self.bytes_per_sample * self.num_channels 270 else: 271 num_bytes = num_samples * self.num_channels // 8 272 return num_bytes
Convert samples to bytes
Parameters
- num_samples (int): the number of samples
Returns
- int: the number of bytes
274 def notify_samples(self, notify_samples : int = None) -> int: 275 """ 276 Set the number of samples to notify the user about 277 278 Parameters 279 ---------- 280 notify_samples : int | pint.Quantity 281 the number of samples to notify the user about 282 """ 283 284 if notify_samples is not None: 285 notify_samples = UnitConversion.convert(notify_samples, units.Sa, int) 286 self._notify_samples = notify_samples 287 self.notify_size = self.samples_to_bytes(self._notify_samples) 288 return self._notify_samples
Set the number of samples to notify the user about
Parameters
- notify_samples (int | pint.Quantity): the number of samples to notify the user about
301 def memory_size(self, memory_samples : int = None) -> int: 302 """ 303 Sets the memory size in samples per channel. The memory size setting must be set before transferring 304 data to the card. (see register `SPC_MEMSIZE` in the manual) 305 306 Parameters 307 ---------- 308 memory_samples : int | pint.Quantity 309 the size of the memory in samples 310 311 Returns 312 ------- 313 int 314 the size of the memory in samples 315 """ 316 317 if memory_samples is not None: 318 memory_samples = UnitConversion.convert(memory_samples, units.Sa, int) 319 self.card.set_i(SPC_MEMSIZE, memory_samples) 320 self._memory_size = self.card.get_i(SPC_MEMSIZE) 321 return self._memory_size
Sets the memory size in samples per channel. The memory size setting must be set before transferring
data to the card. (see register SPC_MEMSIZE
in the manual)
Parameters
- memory_samples (int | pint.Quantity): the size of the memory in samples
Returns
- int: the size of the memory in samples
323 def output_buffer_size(self, buffer_samples : int = None) -> int: 324 """ 325 Set the size of the output buffer (see register `SPC_DATA_OUTBUFSIZE` in the manual) 326 327 Parameters 328 ---------- 329 buffer_samples : int | pint.Quantity 330 the size of the output buffer in Bytes 331 332 Returns 333 ------- 334 int 335 the size of the output buffer in Samples 336 """ 337 338 if buffer_samples is not None: 339 buffer_samples = UnitConversion.convert(buffer_samples, units.B, int) 340 buffer_size = self.samples_to_bytes(buffer_samples) 341 self.card.set_i(SPC_DATA_OUTBUFSIZE, buffer_size) 342 return self.bytes_to_samples(self.card.get_i(SPC_DATA_OUTBUFSIZE))
Set the size of the output buffer (see register SPC_DATA_OUTBUFSIZE
in the manual)
Parameters
- buffer_samples (int | pint.Quantity): the size of the output buffer in Bytes
Returns
- int: the size of the output buffer in Samples
347 def pre_trigger(self, num_samples : int = None) -> int: 348 """ 349 Set the number of pre trigger samples (see register `SPC_PRETRIGGER` in the manual) 350 351 Parameters 352 ---------- 353 num_samples : int | pint.Quantity 354 the number of pre trigger samples 355 356 Returns 357 ------- 358 int 359 the number of pre trigger samples 360 """ 361 362 if num_samples is not None: 363 num_samples = UnitConversion.convert(num_samples, units.Sa, int) 364 self.card.set_i(SPC_PRETRIGGER, num_samples) 365 self._pre_trigger = self.card.get_i(SPC_PRETRIGGER) 366 return self._pre_trigger
Set the number of pre trigger samples (see register SPC_PRETRIGGER
in the manual)
Parameters
- num_samples (int | pint.Quantity): the number of pre trigger samples
Returns
- int: the number of pre trigger samples
368 def post_trigger(self, num_samples : int = None) -> int: 369 """ 370 Set the number of post trigger samples (see register `SPC_POSTTRIGGER` in the manual) 371 372 Parameters 373 ---------- 374 num_samples : int | pint.Quantity 375 the number of post trigger samples 376 377 Returns 378 ------- 379 int 380 the number of post trigger samples 381 """ 382 383 if self._memory_size < num_samples: 384 raise ValueError(f"The number of post trigger samples needs to be smaller than the total number of samples: {self._memory_size} < {num_samples}") 385 if num_samples is not None: 386 num_samples = UnitConversion.convert(num_samples, units.Sa, int) 387 self.card.set_i(SPC_POSTTRIGGER, num_samples) 388 post_trigger = self.card.get_i(SPC_POSTTRIGGER) 389 self._pre_trigger = self._memory_size - post_trigger 390 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
392 def allocate_buffer(self, num_samples : int, no_reshape = False) -> None: 393 """ 394 Memory allocation for the buffer that is used for communicating with the card 395 396 Parameters 397 ---------- 398 num_samples : int | pint.Quantity = None 399 use the number of samples an get the number of active channels and bytes per samples directly from the card 400 """ 401 402 self.buffer_samples = UnitConversion.convert(num_samples, units.Sa, int) 403 404 sample_type = self.numpy_type() 405 406 dwMask = self._buffer_alignment - 1 407 408 item_size = sample_type(0).itemsize 409 # print(f"Item size: {item_size}") 410 # allocate a buffer (numpy array) for DMA transfer: a little bigger one to have room for address alignment 411 databuffer_unaligned = np.empty(((self._buffer_alignment + self.buffer_size) // item_size, ), dtype = sample_type) # byte count to sample (// = integer division) 412 # two numpy-arrays may share the same memory: skip the begin up to the alignment boundary (ArrayVariable[SKIP_VALUE:]) 413 # Address of data-memory from numpy-array: ArrayVariable.__array_interface__['data'][0] 414 start_pos_samples = ((self._buffer_alignment - (databuffer_unaligned.__array_interface__['data'][0] & dwMask)) // item_size) 415 self.buffer = databuffer_unaligned[start_pos_samples:start_pos_samples + (self.buffer_size // item_size)] # byte address to sample size 416 if self.bits_per_sample == 1: 417 self.unpackbits() # allocate the bit buffer for digital cards 418 elif not self._12bit_mode and not no_reshape: 419 self.buffer = self.buffer.reshape((self.num_channels, self.buffer_samples), order='F') # index definition: [channel, sample] !
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
421 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: 422 """ 423 Start the transfer of the data to or from the card (see the API function `spcm_dwDefTransfer_i64` in the manual) 424 425 Parameters 426 ---------- 427 *args : list 428 list of additonal arguments that are added as flags to the start dma command 429 buffer_type : int 430 the type of buffer that is used for the transfer 431 direction : int 432 the direction of the transfer 433 notify_samples : int 434 the number of samples to notify the user about 435 transfer_offset : int 436 the offset of the transfer 437 transfer_length : int 438 the length of the transfer 439 exception_num_samples : bool 440 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. 441 442 Raises 443 ------ 444 SpcmException 445 """ 446 447 self.notify_samples(UnitConversion.convert(notify_samples, units.Sa, int)) 448 transfer_offset = UnitConversion.convert(transfer_offset, units.Sa, int) 449 transfer_length = UnitConversion.convert(transfer_length, units.Sa, int) 450 451 if transfer_length is not None: 452 self.buffer_samples = transfer_length 453 454 if self.buffer is None: 455 raise SpcmException(text="No buffer defined for transfer") 456 if buffer_type: 457 self.buffer_type = buffer_type 458 if direction is None: 459 if self.direction == Direction.Acquisition: 460 direction = SPCM_DIR_CARDTOPC 461 elif self.direction == Direction.Generation: 462 direction = SPCM_DIR_PCTOCARD 463 else: 464 raise SpcmException(text="Please define a direction for transfer (SPCM_DIR_CARDTOPC or SPCM_DIR_PCTOCARD)") 465 466 if self._notify_samples != 0 and np.remainder(self.buffer_samples, self._notify_samples) and exception_num_samples: 467 raise SpcmException("The number of samples needs to be a multiple of the notify samples.") 468 469 if transfer_offset: 470 transfer_offset_bytes = self.samples_to_bytes(transfer_offset) 471 else: 472 transfer_offset_bytes = 0 473 474 # we define the buffer for transfer 475 self.card._print("Starting the DMA transfer and waiting until data is in board memory") 476 self._c_buffer = self.buffer.ctypes.data_as(c_void_p) 477 self.card._check_error(spcm_dwDefTransfer_i64(self.card._handle, self.buffer_type, direction, self.notify_size, self._c_buffer, transfer_offset_bytes, self.buffer_size)) 478 479 # Execute additional commands if available 480 if args: 481 cmd = 0 482 for arg in args: 483 cmd |= arg 484 self.card.cmd(cmd) 485 self.card._print("... data transfer started")
Start the transfer of the data to or from the card (see the API function spcm_dwDefTransfer_i64
in the manual)
Parameters
- *args (list): list of additonal arguments that are added as flags to the start dma command
- buffer_type (int): the type of buffer that is used for the transfer
- direction (int): the direction of the transfer
- notify_samples (int): the number of samples to notify the user about
- transfer_offset (int): the offset of the transfer
- transfer_length (int): the length of the transfer
- exception_num_samples (bool): if True, an exception is raised if the number of samples is not a multiple of the notify samples. The automatic buffer handling only works with the number of samples being a multiple of the notify samples.
Raises
- SpcmException
487 def duration(self, duration : pint.Quantity, pre_trigger_duration : pint.Quantity = None, post_trigger_duration : pint.Quantity = None) -> None: 488 """ 489 Set the duration of the data transfer 490 491 Parameters 492 ---------- 493 duration : pint.Quantity 494 the duration of the data transfer 495 pre_trigger_duration : pint.Quantity = None 496 the duration before the trigger event 497 post_trigger_duration : pint.Quantity = None 498 the duration after the trigger event 499 500 Returns 501 ------- 502 pint.Quantity 503 the duration of the data transfer 504 """ 505 506 if pre_trigger_duration is None and post_trigger_duration is None: 507 raise ValueError("Please define either pre_trigger_duration or post_trigger_duration") 508 509 memsize_min = self.card.get_i(SPC_AVAILMEMSIZE_MIN) 510 memsize_max = self.card.get_i(SPC_AVAILMEMSIZE_MAX) 511 memsize_stp = self.card.get_i(SPC_AVAILMEMSIZE_STEP) 512 num_samples = (duration * self._sample_rate()).to_base_units().magnitude 513 num_samples = np.ceil(num_samples / memsize_stp) * memsize_stp 514 num_samples = np.clip(num_samples, memsize_min, memsize_max) 515 num_samples = int(num_samples) 516 self.memory_size(num_samples) 517 self.allocate_buffer(num_samples) 518 if pre_trigger_duration is not None: 519 pre_min = self.card.get_i(SPC_AVAILPRETRIGGER_MIN) 520 pre_max = self.card.get_i(SPC_AVAILPRETRIGGER_MAX) 521 pre_stp = self.card.get_i(SPC_AVAILPRETRIGGER_STEP) 522 pre_samples = (pre_trigger_duration * self._sample_rate()).to_base_units().magnitude 523 pre_samples = np.ceil(pre_samples / pre_stp) * pre_stp 524 pre_samples = np.clip(pre_samples, pre_min, pre_max) 525 pre_samples = int(post_samples) 526 self.post_trigger(post_samples) 527 if post_trigger_duration is not None: 528 post_min = self.card.get_i(SPC_AVAILPOSTTRIGGER_MIN) 529 post_max = self.card.get_i(SPC_AVAILPOSTTRIGGER_MAX) 530 post_stp = self.card.get_i(SPC_AVAILPOSTTRIGGER_STEP) 531 post_samples = (post_trigger_duration * self._sample_rate()).to_base_units().magnitude 532 post_samples = np.ceil(post_samples / post_stp) * post_stp 533 post_samples = np.clip(post_samples, post_min, post_max) 534 post_samples = int(post_samples) 535 self.post_trigger(post_samples) 536 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
538 def time_data(self, total_num_samples : int = None, return_units = units.s) -> npt.NDArray: 539 """ 540 Get the time array for the data buffer 541 542 Parameters 543 ---------- 544 total_num_samples : int | pint.Quantity 545 the total number of samples 546 return_units : pint.Quantity 547 the units that the time should be converted to 548 549 Returns 550 ------- 551 numpy array 552 the time array 553 """ 554 555 if total_num_samples is None: 556 total_num_samples = self._buffer_samples 557 total_num_samples = UnitConversion.convert(total_num_samples, units.Sa, int) 558 pre_trigger = UnitConversion.convert(self._pre_trigger, units.Sa, int) 559 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
561 def convert_time(self, time, return_units = units.s): 562 """ 563 Convert a time to the units of the card sample rate 564 565 Parameters 566 ---------- 567 time : numpy array 568 the time array with integers that should be converted 569 return_units : numpy array with pint.Quantity 570 the units that the time should be converted to 571 572 Returns 573 ------- 574 pint.Quantity 575 the converted time 576 """ 577 578 sample_rate = self._sample_rate() 579 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
581 def unpack_12bit_buffer(self, data : npt.NDArray[np.int_] = None) -> npt.NDArray[np.int_]: 582 """ 583 Unpacks the 12-bit packed data to 16-bit data 584 585 Parameters 586 ---------- 587 data : numpy array 588 the packed data 589 590 Returns 591 ------- 592 numpy array 593 the unpacked 16bit buffer 594 """ 595 596 if not self._12bit_mode: 597 raise SpcmException("The card is not in 12bit packed mode") 598 599 if data is None: 600 data = self.buffer 601 602 fst_int8, mid_int8, lst_int8 = np.reshape(data, (data.shape[0] // 3, 3)).astype(np.int16).T 603 nibble_h = (mid_int8 >> 0) & 0x0F 604 nibble_m = (fst_int8 >> 4) & 0x0F 605 nibble_l = (fst_int8 >> 0) & 0x0F 606 fst_int12 = ((nibble_h << 12) >> 4) | (nibble_m << 4) | (nibble_l << 0) 607 nibble_h = (lst_int8 >> 4) & 0x0F 608 nibble_m = (lst_int8 >> 0) & 0x0F 609 nibble_l = (mid_int8 >> 4) & 0x0F 610 snd_int12 = ((nibble_h << 12) >> 4) | (nibble_m << 4) | (nibble_l << 0) 611 data_int12 = np.concatenate((fst_int12[:, None], snd_int12[:, None]), axis=1).reshape((-1,)) 612 data_int12 = data_int12.reshape((self.num_channels, self._buffer_samples), order='F') 613 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
615 def unpackbits(self, data : npt.NDArray[np.int_] = None) -> npt.NDArray[np.int_]: 616 """ 617 Unpack the buffer to bits 618 619 Parameters 620 ---------- 621 data : numpy array | None = None 622 the packed data 623 624 Returns 625 ------- 626 numpy array 627 the unpacked buffer 628 """ 629 630 if data is None: 631 data = self.buffer 632 dshape = list(data.shape) 633 return_data = data.reshape([-1, 1]) 634 num_bits = return_data.dtype.itemsize * 8 635 mask = 2**np.arange(num_bits, dtype=return_data.dtype).reshape([1, num_bits]) 636 self.bit_buffer = (return_data & mask).astype(np.bool).astype(np.uint8).reshape(dshape + [num_bits]) 637 return self.bit_buffer
Unpack the buffer to bits
Parameters
- data (numpy array | None = None): the packed data
Returns
- numpy array: the unpacked buffer
639 def packbits(self) -> None: 640 """ 641 Pack the self.buffer from the self.bit_buffer 642 """ 643 644 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
646 def tofile(self, filename : str, buffer = None, **kwargs) -> None: 647 """ 648 Export the buffer to a file. The file format is determined by the file extension 649 Supported file formats are: 650 * .bin: raw binary file 651 * .csv: comma-separated values file 652 * .npy: numpy binary file 653 * .npz: compressed numpy binary file 654 * .txt: whitespace-delimited text file 655 * .h5: hdf5 file format 656 657 Parameters 658 ---------- 659 filename : str 660 the name of the file that the buffer should be exported to 661 662 Raises 663 ------ 664 ImportError 665 if the file format is not supported 666 """ 667 668 if buffer is None: 669 buffer = self.buffer 670 file_path = Path(filename) 671 if file_path.suffix == '.bin': 672 buffer.tofile(file_path) 673 elif file_path.suffix == '.csv': 674 delimiter = kwargs.get('delimiter', ',') 675 np.savetxt(file_path, buffer, delimiter=delimiter) 676 elif file_path.suffix == '.npy': 677 np.save(file_path, buffer) 678 elif file_path.suffix == '.npz': 679 np.savez_compressed(file_path, buffer) 680 elif file_path.suffix == '.txt': 681 np.savetxt(file_path, buffer, fmt='%d') 682 elif file_path.suffix == '.h5' or file_path.suffix == '.hdf5': 683 import h5py 684 with h5py.File(file_path, 'w') as f: 685 f.create_dataset('data', data=buffer) 686 else: 687 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
689 def fromfile(self, filename : str, in_buffer : bool = True, **kwargs) -> npt.NDArray[np.int_]: 690 """ 691 Import the buffer from a file. The file format is determined by the file extension 692 Supported file formats are: 693 * .bin: raw binary file 694 * .csv: comma-separated values file 695 * .npy: numpy binary file 696 * .npz: compressed numpy binary file 697 * .txt: whitespace-delimited text file 698 * .h5: hdf5 file format 699 700 Parameters 701 ---------- 702 filename : str 703 the name of the file that the buffer should be imported from 704 705 Raises 706 ------ 707 ImportError 708 if the file format is not supported 709 """ 710 711 file_path = Path(filename) 712 if file_path.suffix == '.bin': 713 dtype = kwargs.get('dtype', self.numpy_type()) 714 shape = kwargs.get('shape', (self.num_channels, self.buffer_size // self.num_channels)) 715 buffer = np.fromfile(file_path, dtype=dtype) 716 loaded_data = buffer.reshape(shape, order='C') 717 elif file_path.suffix == '.csv': 718 delimiter = kwargs.get('delimiter', ',') 719 loaded_data = np.loadtxt(file_path, delimiter=delimiter) 720 elif file_path.suffix == '.npy': 721 loaded_data = np.load(file_path) 722 elif file_path.suffix == '.npz': 723 data = np.load(file_path) 724 loaded_data = data['arr_0'] 725 elif file_path.suffix == '.txt': 726 loaded_data = np.loadtxt(file_path) 727 elif file_path.suffix == '.h5' or file_path.suffix == '.hdf5': 728 import h5py 729 with h5py.File(file_path, 'r') as f: 730 loaded_data = f['data'][()] 731 else: 732 raise ImportError("File format not supported") 733 734 if in_buffer: 735 self.buffer[:] = loaded_data 736 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
739 def avail_card_len(self, available_samples : int = 0) -> None: 740 """ 741 Set the amount of data that has been read out of the data buffer (see register `SPC_DATA_AVAIL_CARD_LEN` in the manual) 742 743 Parameters 744 ---------- 745 available_samples : int | pint.Quantity 746 the amount of data that is available for reading 747 """ 748 749 available_samples = UnitConversion.convert(available_samples, units.Sa, int) 750 # print(available_samples, self.bytes_per_sample, self.num_channels) 751 available_bytes = self.samples_to_bytes(available_samples) 752 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
754 def avail_user_pos(self, in_bytes : bool = False) -> int: 755 """ 756 Get the current position of the pointer in the data buffer (see register `SPC_DATA_AVAIL_USER_POS` in the manual) 757 758 Parameters 759 ---------- 760 in_bytes : bool 761 if True, the position is returned in bytes 762 763 Returns 764 ------- 765 int 766 pointer position 767 """ 768 769 self.current_user_pos = self.card.get_i(SPC_DATA_AVAIL_USER_POS) 770 if not in_bytes: 771 self.current_user_pos = self.bytes_to_samples(self.current_user_pos) 772 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
774 def avail_user_len(self, in_bytes : bool = False) -> int: 775 """ 776 Get the current length of the data in the data buffer (see register `SPC_DATA_AVAIL_USER_LEN` in the manual) 777 778 Parameters 779 ---------- 780 in_bytes : bool 781 if True, the length is returned in bytes 782 783 Returns 784 ------- 785 int 786 data length available 787 """ 788 789 user_len = self.card.get_i(SPC_DATA_AVAIL_USER_LEN) 790 if not in_bytes: 791 user_len = self.bytes_to_samples(user_len) 792 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
794 def fill_size_promille(self, return_unit = None) -> int: 795 """ 796 Get the fill size of the data buffer (see register `SPC_FILLSIZEPROMILLE` in the manual) 797 798 Returns 799 ------- 800 int 801 fill size 802 """ 803 804 return_value = self.card.get_i(SPC_FILLSIZEPROMILLE) 805 if return_unit is not None: return_value = UnitConversion.to_unit(return_value * units.promille, return_unit) 806 return return_value
Get the fill size of the data buffer (see register SPC_FILLSIZEPROMILLE
in the manual)
Returns
- int: fill size
808 def wait_dma(self) -> None: 809 """ 810 Wait for the DMA transfer to finish (see register `M2CMD_DATA_WAITDMA` in the manual) 811 """ 812 813 self.card.cmd(M2CMD_DATA_WAITDMA)
Wait for the DMA transfer to finish (see register M2CMD_DATA_WAITDMA
in the manual)
808 def wait_dma(self) -> None: 809 """ 810 Wait for the DMA transfer to finish (see register `M2CMD_DATA_WAITDMA` in the manual) 811 """ 812 813 self.card.cmd(M2CMD_DATA_WAITDMA)
Wait for the DMA transfer to finish (see register M2CMD_DATA_WAITDMA
in the manual)
816 def numpy_type(self) -> npt.NDArray[np.int_]: 817 """ 818 Get the type of numpy data from number of bytes 819 820 Returns 821 ------- 822 numpy data type 823 the type of data that is used by the card 824 """ 825 826 if self._8bit_mode: 827 return np.uint8 828 if self._12bit_mode: 829 return np.int8 830 if self.bits_per_sample == 1: 831 if self.num_channels <= 8: 832 return np.uint8 833 elif self.num_channels <= 16: 834 return np.uint16 835 elif self.num_channels <= 32: 836 return np.uint32 837 return np.uint64 838 if self.bits_per_sample <= 8: 839 return np.int8 840 elif self.bits_per_sample <= 16: 841 return np.int16 842 elif self.bits_per_sample <= 32: 843 return np.int32 844 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
847 def data_conversion(self, mode : int = None) -> int: 848 """ 849 Set the data conversion mode (see register `SPC_DATACONVERSION` in the manual) 850 851 Parameters 852 ---------- 853 mode : int 854 the data conversion mode 855 """ 856 857 if mode is not None: 858 self.card.set_i(SPC_DATACONVERSION, mode) 859 mode = self.card.get_i(SPC_DATACONVERSION) 860 self._8bit_mode = (mode == SPCM_DC_12BIT_TO_8BIT or mode == SPCM_DC_14BIT_TO_8BIT or mode == SPCM_DC_16BIT_TO_8BIT) 861 self._12bit_mode = (mode == SPCM_DC_12BIT_TO_12BITPACKED) 862 self._bits_per_sample() 863 self._bytes_per_sample() 864 return mode
Set the data conversion mode (see register SPC_DATACONVERSION
in the manual)
Parameters
- mode (int): the data conversion mode
866 def avail_data_conversion(self) -> int: 867 """ 868 Get the available data conversion modes (see register `SPC_AVAILDATACONVERSION` in the manual) 869 870 Returns 871 ------- 872 int 873 the available data conversion modes 874 """ 875 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
887 def verbose(self, verbose : bool = None) -> bool: 888 """ 889 Set or get the verbose mode for the data transfer 890 891 Parameters 892 ---------- 893 verbose : bool = None 894 the verbose mode 895 """ 896 897 if verbose is not None: 898 self._verbose = verbose 899 return self._verbose
Set or get the verbose mode for the data transfer
Parameters
- verbose (bool = None): the verbose mode
901 def to_transfer_samples(self, samples) -> None: 902 """ 903 This method sets the number of samples to transfer 904 905 Parameters 906 ---------- 907 samples : int | pint.Quantity 908 the number of samples to transfer 909 """ 910 911 samples = UnitConversion.convert(samples, units.Sa, int) 912 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
927 def polling(self, polling : bool = True, timer : float = 0.01) -> None: 928 """ 929 Set the polling mode for the data transfer otherwise wait_dma() is used 930 931 Parameters 932 ---------- 933 polling : bool 934 True to enable polling, False to disable polling 935 timer : float | pint.Quantity 936 the polling timer in seconds 937 """ 938 939 self._polling = polling 940 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
943 def auto_avail_card_len(self, value : bool = None) -> bool: 944 """ 945 Enable or disable the automatic sending of the number of samples that the card can now use for sample data transfer again 946 947 Parameters 948 ---------- 949 value : bool = None 950 True to enable, False to disable and None to get the current status 951 952 Returns 953 ------- 954 bool 955 the current status 956 """ 957 if value is not None: 958 self._auto_avail_card_len = value 959 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
1012 def flush(self): 1013 """ 1014 This method is used to tell the card that a notify size of data is freed up after reading (acquisition) or written to (generation) 1015 """ 1016 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)
248class DDS(CardFunctionality): 249 """a higher-level abstraction of the SpcmCardFunctionality class to implement DDS functionality 250 251 The DDS firmware allows the user a certain maximum number of dds cores, that 252 each on it's own generates a sine wave with the following parameters: 253 * static parameters: 254 + frequency 255 + amplitude 256 + phase 257 * dynamic parameters: 258 + frequency_slope 259 changes the active frequency of the dds core with a linear slope 260 + amplitude_slope 261 changes the active amplitude of the dds core with a linear slope 262 Each of these cores can either be added together and outputted, or specific groups 263 of cores can be added together and outputted on a specific hardware output channel. 264 Furthermore, specific dds cores can be connected to input parameters of another dds core. 265 266 For more information about what setups are available, please have a look at the user manual 267 for your specific card. 268 269 Commands 270 --------- 271 The DDS functionality is controlled through commands that are listed and then written to the card. 272 These written lists of commands are collected in a shadow register and are transferred to 273 the active register when a trigger is received. 274 275 There are three different trigger sources, that can be set with the method 'trg_source()': 276 * SPCM_DDS_TRG_SRC_NONE = 0 277 no triggers are generated and the commands are only transfered to the active register 278 when a exec_now command is send 279 * SPCM_DDS_TRG_SRC_TIMER = 1 280 the triggers are generated on a timed grid with a period that can be set by the 281 method 'trg_timer()' 282 * SPCM_DDS_TRG_SRC_CARD = 2 283 the triggers come from the card internal trigger logic (for more information, 284 see our product's user manual on how to setup the different triggers). In the DDS-mode 285 multiple triggers can be processed, as with the mode SPC_STD_REP_SINGLERESTART. 286 287 Note 288 ---- 289 also the trigger source setting happens when a trigger comes. Hence a change of 290 the trigger mode only happens after an 'arm()' command was send and an internal trigger was 291 received. 292 293 """ 294 295 cores : list[DDSCore] = [] 296 channels : Channels = None 297 298 check_features : bool = False 299 300 _current_core : int = -1 301 _channel_from_core : dict[int, int] = {} 302 303 def __init__(self, *args, **kwargs) -> None: 304 super().__init__(*args, **kwargs) 305 self.channels = kwargs.get("channels", None) 306 self.check_features = kwargs.get("check_features", False) 307 self.cores = [] 308 self.load_cores() 309 # Check if DDS feature is installed 310 if self.check_features: 311 features = self.card.ext_features() 312 if not ((features & SPCM_FEAT_EXTFW_DDS20) or (features & SPCM_FEAT_EXTFW_DDS50)): 313 raise SpcmException("The DDS feature is not installed on the card") 314 315 def load_cores(self): 316 """ 317 load the cores of the DDS functionality 318 """ 319 320 self.cores = [] 321 num_cores = self.num_cores() 322 323 if self.channels is not None: 324 for channel in self.channels: 325 cores_on_channel = self.get_cores_on_channel(channel.index) 326 for core in range(num_cores): 327 if cores_on_channel & (1 << core): 328 self._channel_from_core[core] = channel 329 330 for core in range(num_cores): 331 if core in self._channel_from_core: 332 self.cores.append(DDSCore(core, self, channel=self._channel_from_core[core])) 333 else: 334 self.cores.append(DDSCore(core, self)) 335 336 def __len__(self) -> int: 337 """ 338 get the number of cores 339 340 Returns 341 ------- 342 int 343 the number of cores 344 """ 345 return len(self.cores) 346 347 def __iter__(self): 348 """ 349 make the class iterable 350 351 Returns 352 ------- 353 self 354 """ 355 return self 356 357 def __next__(self): 358 """ 359 get the next core 360 361 Returns 362 ------- 363 DDSCore 364 the next core 365 """ 366 367 self._current_core += 1 368 if self._current_core < len(self.cores): 369 return self.cores[self._current_core] 370 else: 371 self._current_core = -1 372 raise StopIteration 373 374 def __getitem__(self, index : int) -> DDSCore: 375 """ 376 get a specific core 377 378 Parameters 379 ---------- 380 index : int 381 the index of the core 382 383 Returns 384 ------- 385 DDSCore 386 the specific core 387 """ 388 389 return self.cores[index] 390 391 def set_i(self, reg : int, value : int) -> None: 392 """ 393 set an integer value to a register 394 395 Parameters 396 ---------- 397 reg : int 398 the register to be changed 399 value : int 400 the value to be set 401 402 Raises 403 ------ 404 SpcmException 405 if the command list is full 406 """ 407 408 self.card.set_i(reg, value) 409 410 def set_d(self, reg : int, value : float) -> None: 411 """ 412 set a double value to a register 413 414 Parameters 415 ---------- 416 reg : int 417 the register to be changed 418 value : float 419 the value to be set 420 """ 421 422 self.card.set_d(reg, value) 423 424 def reset(self) -> None: 425 """ 426 Resets the DDS specific part of the firmware (see register `SPC_DDS_CMD` in the manual) 427 """ 428 429 self.cmd(SPCM_DDS_CMD_RESET) 430 431 # DDS information 432 def num_cores(self) -> int: 433 """ 434 get the total num of available cores on the card. (see register `SPC_DDS_NUM_CORES` in the manual) 435 436 Returns 437 ------- 438 int 439 the available number of dds cores 440 """ 441 return self.card.get_i(SPC_DDS_NUM_CORES) 442 443 def queue_cmd_max(self): 444 """ 445 get the total number of commands that can be hold by the queue. (see register `SPC_DDS_QUEUE_CMD_MAX` in the manual) 446 447 Returns 448 ------- 449 int 450 the total number of commands 451 """ 452 return self.card.get_i(SPC_DDS_QUEUE_CMD_MAX) 453 454 def queue_cmd_count(self): 455 """ 456 get the current number of commands that are in the queue. (see register `SPC_DDS_QUEUE_CMD_COUNT` in the manual) 457 458 Returns 459 ------- 460 int 461 the current number of commands 462 """ 463 return self.card.get_i(SPC_DDS_QUEUE_CMD_COUNT) 464 465 def status(self): 466 return self.card.get_i(SPC_DDS_STATUS) 467 468 # DDS setup settings 469 def data_transfer_mode(self, mode : int) -> None: 470 """ 471 set the data transfer mode for the DDS functionality (see register `SPC_DDS_DATA_TRANSFER_MODE` in the manual) 472 473 Parameters 474 ---------- 475 mode : int 476 the data transfer mode: 477 * SPCM_DDS_DTM_SINGLE = 0 478 the data is transferred using single commands (with lower latency) 479 * SPCM_DDS_DTM_DMA = 1 480 the data is transferred using DMA (with higher bandwidth) 481 """ 482 483 self._dtm = mode 484 self.set_i(SPC_DDS_DATA_TRANSFER_MODE, mode) 485 486 def get_data_transfer_mode(self) -> int: 487 """ 488 get the data transfer mode for the DDS functionality (see register `SPC_DDS_DATA_TRANSFER_MODE` in the manual) 489 490 Returns 491 ------- 492 int 493 the data transfer mode: 494 * SPCM_DDS_DTM_SINGLE = 0 495 the data is transferred using single commands (with lower latency) 496 * SPCM_DDS_DTM_DMA = 1 497 the data is transferred using DMA (with higher bandwidth) 498 """ 499 500 self._dtm = self.card.get_i(SPC_DDS_DATA_TRANSFER_MODE) 501 return self._dtm 502 503 def phase_behaviour(self, behaviour : int) -> None: 504 """ 505 set the phase behaviour of the DDS cores (see register `SPC_DDS_PHASE_BEHAVIOUR` in the manual) 506 507 Parameters 508 ---------- 509 behaviour : int 510 the phase behaviour 511 """ 512 513 self.set_i(SPC_DDS_PHASE_BEHAVIOUR, behaviour) 514 515 def get_phase_behaviour(self) -> int: 516 """ 517 get the phase behaviour of the DDS cores (see register `SPC_DDS_PHASE_BEHAVIOUR` in the manual) 518 519 Returns 520 ------- 521 int 522 the phase behaviour 523 """ 524 525 return self.card.get_i(SPC_DDS_PHASE_BEHAVIOUR) 526 527 def cores_on_channel(self, channel : int, *args) -> None: 528 """ 529 setup the cores that are connected to a specific channel (see register `SPC_DDS_CORES_ON_CH0` in the manual) 530 531 Parameters 532 ---------- 533 channel : int 534 the channel number 535 *args : int 536 the cores that are connected to the channel 537 538 TODO: change the channel associated with each core 539 """ 540 541 mask = 0 542 for core in args: 543 mask |= core 544 self.set_i(SPC_DDS_CORES_ON_CH0 + channel, mask) 545 546 def get_cores_on_channel(self, channel : int) -> int: 547 """ 548 get the cores that are connected to a specific channel (see register `SPC_DDS_CORES_ON_CH0` in the manual) 549 550 Parameters 551 ---------- 552 channel : int 553 the channel number 554 555 Returns 556 ------- 557 int 558 the cores that are connected to the channel 559 """ 560 561 return self.card.get_i(SPC_DDS_CORES_ON_CH0 + channel) 562 563 def trg_src(self, src : int) -> None: 564 """ 565 setup the source of where the trigger is coming from (see register `SPC_DDS_TRG_SRC` in the manual) 566 567 NOTE 568 --- 569 the trigger source is also set using the shadow register, hence only after an exec_at_trig or exec_now -- 570 571 Parameters 572 ---------- 573 src : int 574 set the trigger source: 575 * SPCM_DDS_TRG_SRC_NONE = 0 576 no trigger source set, only exec_now changes what is output by the cores 577 * SPCM_DDS_TRG_SRC_TIMER = 1 578 an internal timer sends out triggers with a period defined by `trg_timer(period)` 579 * SPCM_DDS_TRG_SRC_CARD = 2 580 use the trigger engine of the card (see the user manual for more information about setting up the trigger engine) 581 """ 582 583 self.set_i(SPC_DDS_TRG_SRC, src) 584 585 def get_trg_src(self) -> int: 586 """ 587 get the source of where the trigger is coming from (see register `SPC_DDS_TRG_SRC` in the manual) 588 589 NOTE 590 ---- 591 the trigger source is also set using the shadow register, hence only after an exec_at_trig or exec_now -- 592 593 Returns 594 ---------- 595 int 596 get one of the trigger source: 597 * SPCM_DDS_TRG_SRC_NONE = 0 598 no trigger source set, only exec_now changes what is output by the cores 599 * SPCM_DDS_TRG_SRC_TIMER = 1 600 an internal timer sends out triggers with a period defined by `trg_timer(period)` 601 * SPCM_DDS_TRG_SRC_CARD = 2 602 use the trigger engine of the card (see the user manual for more information about setting up the trigger engine) 603 """ 604 605 return self.card.get_i(SPC_DDS_TRG_SRC) 606 607 def trg_timer(self, period : float) -> None: 608 """ 609 set the period at which the timer should raise DDS trigger events. (see register `SPC_DDS_TRG_TIMER` in the manual) 610 611 NOTE 612 ---- 613 only used in conjecture with the trigger source set to SPCM_DDS_TRG_SRC_TIMER --- 614 615 Parameters 616 ---------- 617 period : float | pint.Quantity 618 the time between DDS trigger events in seconds 619 """ 620 621 period = UnitConversion.convert(period, units.s, float, rounding=None) 622 self.set_d(SPC_DDS_TRG_TIMER, float(period)) 623 624 def get_trg_timer(self, return_unit = None) -> float: 625 """ 626 get the period at which the timer should raise DDS trigger events. (see register `SPC_DDS_TRG_TIMER` in the manual) 627 628 NOTE 629 ---- 630 only used in conjecture with the trigger source set to SPCM_DDS_TRG_SRC_TIMER --- 631 632 Parameters 633 ---------- 634 return_unit : pint.Unit = None 635 the unit of the returned time between DDS trigger events, by default None 636 637 Returns 638 ---------- 639 float 640 the time between DDS trigger events in seconds 641 """ 642 643 return_value = self.card.get_d(SPC_DDS_TRG_TIMER) 644 if return_unit is not None: return_value = UnitConversion.to_unit(return_value * units.s, return_unit) 645 return return_value 646 647 def x_mode(self, xio : int, mode : int) -> None: 648 """ 649 setup the kind of output that the XIO outputs will give (see register `SPC_DDS_X0_MODE` in the manual) 650 651 Parameters 652 ---------- 653 xio : int 654 the XIO channel number 655 mode : int 656 the mode that the channel needs to run in 657 """ 658 659 self.set_i(SPC_DDS_X0_MODE + xio, mode) 660 661 def get_x_mode(self, xio : int) -> int: 662 """ 663 get the kind of output that the XIO outputs will give (see register `SPC_DDS_X0_MODE` in the manual) 664 665 Parameters 666 ---------- 667 xio : int 668 the XIO channel number 669 670 Returns 671 ------- 672 int 673 the mode that the channel needs to run in 674 SPC_DDS_XIO_SEQUENCE = 0 675 turn on and off the XIO channels using commands in the DDS cmd queue 676 SPC_DDS_XIO_ARM = 1 677 when the DDS firmware is waiting for a trigger to come this signal is high 678 SPC_DDS_XIO_LATCH = 2 679 when the DDS firmware starts executing a change this signal is high 680 """ 681 682 return self.card.get_i(SPC_DDS_X0_MODE + xio) 683 684 def freq_ramp_stepsize(self, divider : int) -> None: 685 """ 686 number of timesteps before the frequency is changed during a frequency ramp. (see register `SPC_DDS_FREQ_RAMP_STEPSIZE` in the manual) 687 688 NOTES 689 ----- 690 - this is a global setting for all cores 691 - 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 692 693 Parameters 694 ---------- 695 divider : int 696 the number of DDS timesteps that a value is kept constant during a frequency ramp 697 """ 698 699 self.set_i(SPC_DDS_FREQ_RAMP_STEPSIZE, int(divider)) 700 701 def get_freq_ramp_stepsize(self) -> int: 702 """ 703 get the number of timesteps before the frequency is changed during a frequency ramp. (see register `SPC_DDS_FREQ_RAMP_STEPSIZE` in the manual) 704 705 NOTES 706 ----- 707 - this is a global setting for all cores 708 - 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 709 710 Returns 711 ---------- 712 divider : int 713 the number of DDS timesteps that a value is kept constant during a frequency ramp 714 """ 715 716 return self.card.get_i(SPC_DDS_FREQ_RAMP_STEPSIZE) 717 718 def amp_ramp_stepsize(self, divider : int) -> None: 719 """ 720 number of timesteps before the amplitude is changed during a frequency ramp. (see register `SPC_DDS_AMP_RAMP_STEPSIZE` in the manual) 721 722 NOTES 723 ----- 724 - this is a global setting for all cores 725 - internally the time divider is used to calculate the amount of change per event using a given amplitude slope, 726 please set the time divider before setting the amplitude slope 727 728 Parameters 729 ---------- 730 divider : int 731 the number of DDS timesteps that a value is kept constant during an amplitude ramp 732 """ 733 734 self.set_i(SPC_DDS_AMP_RAMP_STEPSIZE, int(divider)) 735 736 def get_amp_ramp_stepsize(self) -> int: 737 """ 738 get the number of timesteps before the amplitude is changed during a frequency ramp. (see register `SPC_DDS_AMP_RAMP_STEPSIZE` in the manual) 739 740 NOTES 741 ----- 742 - this is a global setting for all cores 743 - internally the time divider is used to calculate the amount of change per event using a given amplitude slope, 744 please set the time divider before setting the amplitude slope 745 746 Returns 747 ---------- 748 divider : int 749 the number of DDS timesteps that a value is kept constant during an amplitude ramp 750 """ 751 752 return self.card.get_i(SPC_DDS_AMP_RAMP_STEPSIZE) 753 754 # DDS "static" parameters 755 # def amp(self, core_index : int, amplitude : float) -> None: 756 def amp(self, *args) -> None: 757 """ 758 set the amplitude of the sine wave of a specific core (see register `SPC_DDS_CORE0_AMP` in the manual) 759 760 Parameters 761 ---------- 762 core_index : int (optional) 763 the index of the core to be changed 764 amplitude : float 765 the value between 0 and 1 corresponding to the amplitude 766 """ 767 768 if len(args) == 1: 769 amplitude = args[0] 770 for core in self.cores: 771 core.amp(amplitude) 772 elif len(args) == 2: 773 core_index, amplitude = args 774 self.cores[core_index].amp(amplitude) 775 else: 776 raise TypeError("amp() takes 1 or 2 positional arguments ({} given)".format(len(args) + 1)) 777 # self.set_d(SPC_DDS_CORE0_AMP + core_index, float(amplitude)) 778 # aliases 779 amplitude = amp 780 781 def get_amp(self, core_index : int, return_unit = None) -> float: 782 """ 783 gets the amplitude of the sine wave of a specific core (see register `SPC_DDS_CORE0_AMP` in the manual) 784 785 Parameters 786 ---------- 787 core_index : int 788 the index of the core to be changed 789 return_unit : pint.Unit = None 790 the unit of the returned amplitude, by default None 791 792 Returns 793 ------- 794 float | pint.Quantity 795 the value between 0 and 1 corresponding to the amplitude of the specific core or in the specified unit 796 """ 797 798 return self.cores[core_index].get_amp(return_unit) 799 # return self.card.get_d(SPC_DDS_CORE0_AMP + core_index) 800 # aliases 801 get_amplitude = get_amp 802 803 def avail_amp_min(self) -> float: 804 """ 805 get the minimum available amplitude (see register `SPC_DDS_AVAIL_AMP_MIN` in the manual) 806 807 Returns 808 ------- 809 float 810 the minimum available amplitude 811 812 TODO: unitize! 813 """ 814 815 return self.card.get_d(SPC_DDS_AVAIL_AMP_MIN) 816 817 def avail_amp_max(self) -> float: 818 """ 819 get the maximum available amplitude (see register `SPC_DDS_AVAIL_AMP_MAX` in the manual) 820 821 Returns 822 ------- 823 float 824 the maximum available amplitude 825 826 TODO: unitize! 827 """ 828 829 return self.card.get_d(SPC_DDS_AVAIL_AMP_MAX) 830 831 def avail_amp_step(self) -> float: 832 """ 833 get the step size of the available amplitudes (see register `SPC_DDS_AVAIL_AMP_STEP` in the manual) 834 835 Returns 836 ------- 837 float 838 the step size of the available amplitudes 839 840 TODO: unitize! 841 """ 842 843 return self.card.get_d(SPC_DDS_AVAIL_AMP_STEP) 844 845 # def freq(self, core_index : int, frequency : float) -> None: 846 def freq(self, *args) -> None: 847 """ 848 set the frequency of the sine wave of a specific core (see register `SPC_DDS_CORE0_FREQ` in the manual) 849 850 Parameters 851 ---------- 852 core_index : int (optional) 853 the index of the core to be changed 854 frequency : float 855 the value of the frequency in Hz 856 """ 857 858 if len(args) == 1: 859 frequency = args[0] 860 for core in self.cores: 861 core.freq(frequency) 862 elif len(args) == 2: 863 core_index, frequency = args 864 self.cores[core_index].freq(frequency) 865 else: 866 raise TypeError("freq() takes 1 or 2 positional arguments ({} given)".format(len(args) + 1)) 867 # self.set_d(SPC_DDS_CORE0_FREQ + core_index, float(frequency)) 868 # aliases 869 frequency = freq 870 871 def get_freq(self, core_index : int, return_unit = None) -> float: 872 """ 873 gets the frequency of the sine wave of a specific core (see register `SPC_DDS_CORE0_FREQ` in the manual) 874 875 Parameters 876 ---------- 877 core_index : int 878 the index of the core to be changed 879 return_unit : pint.Unit = None 880 the unit of the returned frequency, by default None 881 882 Returns 883 ------- 884 float | pint.Quantity 885 the value of the frequency in Hz the specific core or in the specified unit 886 """ 887 888 return self.cores[core_index].get_freq(return_unit) 889 # aliases 890 get_frequency = get_freq 891 892 def avail_freq_min(self) -> float: 893 """ 894 get the minimum available frequency (see register `SPC_DDS_AVAIL_FREQ_MIN` in the manual) 895 896 Returns 897 ------- 898 float 899 the minimum available frequency 900 901 TODO: unitize! 902 """ 903 904 return self.card.get_d(SPC_DDS_AVAIL_FREQ_MIN) 905 906 def avail_freq_max(self) -> float: 907 """ 908 get the maximum available frequency (see register `SPC_DDS_AVAIL_FREQ_MAX` in the manual) 909 910 Returns 911 ------- 912 float 913 the maximum available frequency 914 915 TODO: unitize! 916 """ 917 918 return self.card.get_d(SPC_DDS_AVAIL_FREQ_MAX) 919 920 def avail_freq_step(self) -> float: 921 """ 922 get the step size of the available frequencies (see register `SPC_DDS_AVAIL_FREQ_STEP` in the manual) 923 924 Returns 925 ------- 926 float 927 the step size of the available frequencies 928 929 TODO: unitize! 930 """ 931 932 return self.card.get_d(SPC_DDS_AVAIL_FREQ_STEP) 933 934 # def phase(self, core_index : int, phase : float) -> None: 935 def phase(self, *args) -> None: 936 """ 937 set the phase of the sine wave of a specific core (see register `SPC_DDS_CORE0_PHASE` in the manual) 938 939 Parameters 940 ---------- 941 core_index : int (optional) 942 the index of the core to be changed 943 phase : float 944 the value between 0 and 360 degrees of the phase 945 """ 946 947 if len(args) == 1: 948 phase = args[0] 949 for core in self.cores: 950 core.phase(phase) 951 elif len(args) == 2: 952 core_index, phase = args 953 self.cores[core_index].phase(phase) 954 else: 955 raise TypeError("phase() takes 1 or 2 positional arguments ({} given)".format(len(args) + 1)) 956 # self.set_d(SPC_DDS_CORE0_PHASE + core_index, float(phase)) 957 958 def get_phase(self, core_index : int, return_unit = None) -> float: 959 """ 960 gets the phase of the sine wave of a specific core (see register `SPC_DDS_CORE0_PHASE` in the manual) 961 962 Parameters 963 ---------- 964 core_index : int 965 the index of the core to be changed 966 return_unit : pint.Unit = None 967 the unit of the returned phase, by default None 968 969 Returns 970 ------- 971 float 972 the value between 0 and 360 degrees of the phase 973 """ 974 975 return self.cores[core_index].get_phase(return_unit) 976 977 def avail_phase_min(self) -> float: 978 """ 979 get the minimum available phase (see register `SPC_DDS_AVAIL_PHASE_MIN` in the manual) 980 981 Returns 982 ------- 983 float 984 the minimum available phase 985 986 TODO: unitize! 987 """ 988 989 return self.card.get_d(SPC_DDS_AVAIL_PHASE_MIN) 990 991 def avail_phase_max(self) -> float: 992 """ 993 get the maximum available phase (see register `SPC_DDS_AVAIL_PHASE_MAX` in the manual) 994 995 Returns 996 ------- 997 float 998 the maximum available phase 999 1000 TODO: unitize! 1001 """ 1002 1003 return self.card.get_d(SPC_DDS_AVAIL_PHASE_MAX) 1004 1005 def avail_phase_step(self) -> float: 1006 """ 1007 get the step size of the available phases (see register `SPC_DDS_AVAIL_PHASE_STEP` in the manual) 1008 1009 Returns 1010 ------- 1011 float 1012 the step size of the available phases 1013 1014 TODO: unitize! 1015 """ 1016 1017 return self.card.get_d(SPC_DDS_AVAIL_PHASE_STEP) 1018 1019 def x_manual_output(self, state_mask : int) -> None: 1020 """ 1021 set the output of the xio channels using a bit mask (see register `SPC_DDS_X_MANUAL_OUTPUT` in the manual) 1022 1023 Parameters 1024 ---------- 1025 state_mask : int 1026 bit mask where the bits correspond to specific channels and 1 to on and 0 to off. 1027 """ 1028 1029 self.set_i(SPC_DDS_X_MANUAL_OUTPUT, state_mask) 1030 1031 def get_x_manual_output(self) -> int: 1032 """ 1033 get the output of the xio channels using a bit mask (see register `SPC_DDS_X_MANUAL_OUTPUT` in the manual) 1034 1035 Returns 1036 ---------- 1037 int 1038 bit mask where the bits correspond to specific channels and 1 to on and 0 to off. 1039 """ 1040 1041 return self.card.get_i(SPC_DDS_X_MANUAL_OUTPUT) 1042 1043 # DDS dynamic parameters 1044 # def freq_slope(self, core_index : int, slope : float) -> None: 1045 def freq_slope(self, *args) -> None: 1046 """ 1047 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) 1048 1049 Parameters 1050 ---------- 1051 core_index : int (optional) 1052 the index of the core to be changed 1053 slope : float 1054 the rate of frequency change in Hz/s 1055 """ 1056 1057 if len(args) == 1: 1058 slope = args[0] 1059 for core in self.cores: 1060 core.freq_slope(slope) 1061 elif len(args) == 2: 1062 core_index, slope = args 1063 self.cores[core_index].freq_slope(slope) 1064 else: 1065 raise TypeError("freq_slope() takes 1 or 2 positional arguments ({} given)".format(len(args) + 1)) 1066 # self.set_d(SPC_DDS_CORE0_FREQ_SLOPE + core_index, float(slope)) 1067 # aliases 1068 frequency_slope = freq_slope 1069 1070 def get_freq_slope(self, core_index : int, return_unit=None) -> float: 1071 """ 1072 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) 1073 1074 Parameters 1075 ---------- 1076 core_index : int 1077 the index of the core to be changed 1078 return_unit : pint.Unit = None 1079 the unit of the returned frequency slope, by default None 1080 1081 Returns 1082 ------- 1083 float 1084 the rate of frequency change in Hz/s 1085 """ 1086 1087 return self.cores[core_index].get_freq_slope(return_unit) 1088 # aliases 1089 get_frequency_slope = get_freq_slope 1090 1091 def avail_freq_slope_min(self) -> float: 1092 """ 1093 get the minimum available frequency slope (see register `SPC_DDS_AVAIL_FREQ_SLOPE_MIN` in the manual) 1094 1095 Returns 1096 ------- 1097 float 1098 the minimum available frequency slope 1099 1100 TODO: unitize! 1101 """ 1102 1103 return self.card.get_d(SPC_DDS_AVAIL_FREQ_SLOPE_MIN) 1104 1105 def avail_freq_slope_max(self) -> float: 1106 """ 1107 get the maximum available frequency slope (see register `SPC_DDS_AVAIL_FREQ_SLOPE_MAX` in the manual) 1108 1109 Returns 1110 ------- 1111 float 1112 the maximum available frequency slope 1113 1114 TODO: unitize! 1115 """ 1116 1117 return self.card.get_d(SPC_DDS_AVAIL_FREQ_SLOPE_MAX) 1118 1119 def avail_freq_slope_step(self) -> float: 1120 """ 1121 get the step size of the available frequency slopes (see register `SPC_DDS_AVAIL_FREQ_SLOPE_STEP` in the manual) 1122 1123 Returns 1124 ------- 1125 float 1126 the step size of the available frequency slopes 1127 1128 TODO: unitize! 1129 """ 1130 1131 return self.card.get_d(SPC_DDS_AVAIL_FREQ_SLOPE_STEP) 1132 1133 # def amp_slope(self, core_index : int, slope : float) -> None: 1134 def amp_slope(self, *args) -> None: 1135 """ 1136 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) 1137 1138 Parameters 1139 ---------- 1140 core_index : int (optional) 1141 the index of the core to be changed 1142 slope : float 1143 the rate of amplitude change in 1/s 1144 """ 1145 1146 if len(args) == 1: 1147 slope = args[0] 1148 for core in self.cores: 1149 core.amp_slope(slope) 1150 elif len(args) == 2: 1151 core_index, slope = args 1152 self.cores[core_index].amp_slope(slope) 1153 else: 1154 raise TypeError("amp_slope() takes 1 or 2 positional arguments ({} given)".format(len(args) + 1)) 1155 # self.set_d(SPC_DDS_CORE0_AMP_SLOPE + core_index, float(slope)) 1156 # aliases 1157 amplitude_slope = amp_slope 1158 1159 def get_amp_slope(self, core_index : int, return_unit = None) -> float: 1160 """ 1161 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) 1162 1163 Parameters 1164 ---------- 1165 core_index : int 1166 the index of the core to be changed 1167 return_unit : pint.Unit = None 1168 the unit of the returned amplitude slope, by default None 1169 1170 Returns 1171 ------- 1172 float 1173 the rate of amplitude change in 1/s 1174 """ 1175 1176 return self.cores[core_index].get_amp_slope(return_unit) 1177 # aliases 1178 amplitude_slope = amp_slope 1179 1180 def avail_amp_slope_min(self) -> float: 1181 """ 1182 get the minimum available amplitude slope (see register `SPC_DDS_AVAIL_AMP_SLOPE_MIN` in the manual) 1183 1184 Returns 1185 ------- 1186 float 1187 the minimum available amplitude slope 1188 1189 TODO: unitize! 1190 """ 1191 1192 return self.card.get_d(SPC_DDS_AVAIL_AMP_SLOPE_MIN) 1193 1194 def avail_amp_slope_max(self) -> float: 1195 """ 1196 get the maximum available amplitude slope (see register `SPC_DDS_AVAIL_AMP_SLOPE_MAX` in the manual) 1197 1198 Returns 1199 ------- 1200 float 1201 the maximum available amplitude slope 1202 1203 TODO: unitize! 1204 """ 1205 1206 return self.card.get_d(SPC_DDS_AVAIL_AMP_SLOPE_MAX) 1207 1208 def avail_amp_slope_step(self) -> float: 1209 """ 1210 get the step size of the available amplitude slopes (see register `SPC_DDS_AVAIL_AMP_SLOPE_STEP` in the manual) 1211 1212 Returns 1213 ------- 1214 float 1215 the step size of the available amplitude slopes 1216 1217 TODO: unitize! 1218 """ 1219 1220 return self.card.get_d(SPC_DDS_AVAIL_AMP_SLOPE_STEP) 1221 1222 # DDS control 1223 def cmd(self, command : int) -> None: 1224 """ 1225 execute a DDS specific control flow command (see register `SPC_DDS_CMD` in the manual) 1226 1227 Parameters 1228 ---------- 1229 command : int 1230 DDS specific command 1231 """ 1232 1233 self.set_i(SPC_DDS_CMD, command) 1234 1235 def exec_at_trg(self) -> None: 1236 """ 1237 execute the commands in the shadow register at the next trigger event (see register `SPC_DDS_CMD` in the manual) 1238 """ 1239 self.cmd(SPCM_DDS_CMD_EXEC_AT_TRG) 1240 # aliases 1241 arm = exec_at_trg 1242 wait_for_trg = exec_at_trg 1243 1244 def exec_now(self) -> None: 1245 """ 1246 execute the commands in the shadow register as soon as possible (see register `SPC_DDS_CMD` in the manual) 1247 """ 1248 1249 self.cmd(SPCM_DDS_CMD_EXEC_NOW) 1250 # aliases 1251 direct_latch = exec_now 1252 1253 def trg_count(self) -> int: 1254 """ 1255 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) 1256 1257 Returns 1258 ------- 1259 int 1260 the number of trigger exec_at_trg and exec_now command that have been executed 1261 """ 1262 1263 return self.card.get_i(SPC_DDS_TRG_COUNT) 1264 1265 def write_to_card(self, flags=0) -> None: 1266 """ 1267 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) 1268 1269 Parameters 1270 ---------- 1271 flags : int = 0 1272 the flags that can be set with the write_to_card command 1273 """ 1274 1275 self.cmd(SPCM_DDS_CMD_WRITE_TO_CARD | flags) 1276 1277 # DDS helper functions 1278 def kwargs2mask(self, kwargs : dict[str, bool], prefix : str = "") -> int: 1279 """ 1280 DDS helper: transform a dictionary with keys with a specific prefix to a bitmask 1281 1282 Parameters 1283 ---------- 1284 kwargs : dict 1285 dictonary with keys with a specific prefix and values given by bools 1286 prefix : str 1287 a prefix for the key names 1288 1289 Returns 1290 ------- 1291 int 1292 bit mask 1293 1294 Example 1295 ------- 1296 ['core_0' = True, 'core_2' = False, 'core_3' = True] => 0b1001 = 9 1297 """ 1298 1299 mask = 0 1300 for keyword, value in kwargs.items(): 1301 bit = int(keyword[len(prefix):]) 1302 if value: 1303 mask |= 1 << bit 1304 else: 1305 mask &= ~(1 << bit) 1306 return mask 1307 # aliases 1308 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.
303 def __init__(self, *args, **kwargs) -> None: 304 super().__init__(*args, **kwargs) 305 self.channels = kwargs.get("channels", None) 306 self.check_features = kwargs.get("check_features", False) 307 self.cores = [] 308 self.load_cores() 309 # Check if DDS feature is installed 310 if self.check_features: 311 features = self.card.ext_features() 312 if not ((features & SPCM_FEAT_EXTFW_DDS20) or (features & SPCM_FEAT_EXTFW_DDS50)): 313 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
315 def load_cores(self): 316 """ 317 load the cores of the DDS functionality 318 """ 319 320 self.cores = [] 321 num_cores = self.num_cores() 322 323 if self.channels is not None: 324 for channel in self.channels: 325 cores_on_channel = self.get_cores_on_channel(channel.index) 326 for core in range(num_cores): 327 if cores_on_channel & (1 << core): 328 self._channel_from_core[core] = channel 329 330 for core in range(num_cores): 331 if core in self._channel_from_core: 332 self.cores.append(DDSCore(core, self, channel=self._channel_from_core[core])) 333 else: 334 self.cores.append(DDSCore(core, self))
load the cores of the DDS functionality
391 def set_i(self, reg : int, value : int) -> None: 392 """ 393 set an integer value to a register 394 395 Parameters 396 ---------- 397 reg : int 398 the register to be changed 399 value : int 400 the value to be set 401 402 Raises 403 ------ 404 SpcmException 405 if the command list is full 406 """ 407 408 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
410 def set_d(self, reg : int, value : float) -> None: 411 """ 412 set a double value to a register 413 414 Parameters 415 ---------- 416 reg : int 417 the register to be changed 418 value : float 419 the value to be set 420 """ 421 422 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
424 def reset(self) -> None: 425 """ 426 Resets the DDS specific part of the firmware (see register `SPC_DDS_CMD` in the manual) 427 """ 428 429 self.cmd(SPCM_DDS_CMD_RESET)
Resets the DDS specific part of the firmware (see register SPC_DDS_CMD
in the manual)
432 def num_cores(self) -> int: 433 """ 434 get the total num of available cores on the card. (see register `SPC_DDS_NUM_CORES` in the manual) 435 436 Returns 437 ------- 438 int 439 the available number of dds cores 440 """ 441 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
443 def queue_cmd_max(self): 444 """ 445 get the total number of commands that can be hold by the queue. (see register `SPC_DDS_QUEUE_CMD_MAX` in the manual) 446 447 Returns 448 ------- 449 int 450 the total number of commands 451 """ 452 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
454 def queue_cmd_count(self): 455 """ 456 get the current number of commands that are in the queue. (see register `SPC_DDS_QUEUE_CMD_COUNT` in the manual) 457 458 Returns 459 ------- 460 int 461 the current number of commands 462 """ 463 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
469 def data_transfer_mode(self, mode : int) -> None: 470 """ 471 set the data transfer mode for the DDS functionality (see register `SPC_DDS_DATA_TRANSFER_MODE` in the manual) 472 473 Parameters 474 ---------- 475 mode : int 476 the data transfer mode: 477 * SPCM_DDS_DTM_SINGLE = 0 478 the data is transferred using single commands (with lower latency) 479 * SPCM_DDS_DTM_DMA = 1 480 the data is transferred using DMA (with higher bandwidth) 481 """ 482 483 self._dtm = mode 484 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)
486 def get_data_transfer_mode(self) -> int: 487 """ 488 get the data transfer mode for the DDS functionality (see register `SPC_DDS_DATA_TRANSFER_MODE` in the manual) 489 490 Returns 491 ------- 492 int 493 the data transfer mode: 494 * SPCM_DDS_DTM_SINGLE = 0 495 the data is transferred using single commands (with lower latency) 496 * SPCM_DDS_DTM_DMA = 1 497 the data is transferred using DMA (with higher bandwidth) 498 """ 499 500 self._dtm = self.card.get_i(SPC_DDS_DATA_TRANSFER_MODE) 501 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)
503 def phase_behaviour(self, behaviour : int) -> None: 504 """ 505 set the phase behaviour of the DDS cores (see register `SPC_DDS_PHASE_BEHAVIOUR` in the manual) 506 507 Parameters 508 ---------- 509 behaviour : int 510 the phase behaviour 511 """ 512 513 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
515 def get_phase_behaviour(self) -> int: 516 """ 517 get the phase behaviour of the DDS cores (see register `SPC_DDS_PHASE_BEHAVIOUR` in the manual) 518 519 Returns 520 ------- 521 int 522 the phase behaviour 523 """ 524 525 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
527 def cores_on_channel(self, channel : int, *args) -> None: 528 """ 529 setup the cores that are connected to a specific channel (see register `SPC_DDS_CORES_ON_CH0` in the manual) 530 531 Parameters 532 ---------- 533 channel : int 534 the channel number 535 *args : int 536 the cores that are connected to the channel 537 538 TODO: change the channel associated with each core 539 """ 540 541 mask = 0 542 for core in args: 543 mask |= core 544 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):
546 def get_cores_on_channel(self, channel : int) -> int: 547 """ 548 get the cores that are connected to a specific channel (see register `SPC_DDS_CORES_ON_CH0` in the manual) 549 550 Parameters 551 ---------- 552 channel : int 553 the channel number 554 555 Returns 556 ------- 557 int 558 the cores that are connected to the channel 559 """ 560 561 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
563 def trg_src(self, src : int) -> None: 564 """ 565 setup the source of where the trigger is coming from (see register `SPC_DDS_TRG_SRC` in the manual) 566 567 NOTE 568 --- 569 the trigger source is also set using the shadow register, hence only after an exec_at_trig or exec_now -- 570 571 Parameters 572 ---------- 573 src : int 574 set the trigger source: 575 * SPCM_DDS_TRG_SRC_NONE = 0 576 no trigger source set, only exec_now changes what is output by the cores 577 * SPCM_DDS_TRG_SRC_TIMER = 1 578 an internal timer sends out triggers with a period defined by `trg_timer(period)` 579 * SPCM_DDS_TRG_SRC_CARD = 2 580 use the trigger engine of the card (see the user manual for more information about setting up the trigger engine) 581 """ 582 583 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)
585 def get_trg_src(self) -> int: 586 """ 587 get the source of where the trigger is coming from (see register `SPC_DDS_TRG_SRC` in the manual) 588 589 NOTE 590 ---- 591 the trigger source is also set using the shadow register, hence only after an exec_at_trig or exec_now -- 592 593 Returns 594 ---------- 595 int 596 get one of the trigger source: 597 * SPCM_DDS_TRG_SRC_NONE = 0 598 no trigger source set, only exec_now changes what is output by the cores 599 * SPCM_DDS_TRG_SRC_TIMER = 1 600 an internal timer sends out triggers with a period defined by `trg_timer(period)` 601 * SPCM_DDS_TRG_SRC_CARD = 2 602 use the trigger engine of the card (see the user manual for more information about setting up the trigger engine) 603 """ 604 605 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)
607 def trg_timer(self, period : float) -> None: 608 """ 609 set the period at which the timer should raise DDS trigger events. (see register `SPC_DDS_TRG_TIMER` in the manual) 610 611 NOTE 612 ---- 613 only used in conjecture with the trigger source set to SPCM_DDS_TRG_SRC_TIMER --- 614 615 Parameters 616 ---------- 617 period : float | pint.Quantity 618 the time between DDS trigger events in seconds 619 """ 620 621 period = UnitConversion.convert(period, units.s, float, rounding=None) 622 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
624 def get_trg_timer(self, return_unit = None) -> float: 625 """ 626 get the period at which the timer should raise DDS trigger events. (see register `SPC_DDS_TRG_TIMER` in the manual) 627 628 NOTE 629 ---- 630 only used in conjecture with the trigger source set to SPCM_DDS_TRG_SRC_TIMER --- 631 632 Parameters 633 ---------- 634 return_unit : pint.Unit = None 635 the unit of the returned time between DDS trigger events, by default None 636 637 Returns 638 ---------- 639 float 640 the time between DDS trigger events in seconds 641 """ 642 643 return_value = self.card.get_d(SPC_DDS_TRG_TIMER) 644 if return_unit is not None: return_value = UnitConversion.to_unit(return_value * units.s, return_unit) 645 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
647 def x_mode(self, xio : int, mode : int) -> None: 648 """ 649 setup the kind of output that the XIO outputs will give (see register `SPC_DDS_X0_MODE` in the manual) 650 651 Parameters 652 ---------- 653 xio : int 654 the XIO channel number 655 mode : int 656 the mode that the channel needs to run in 657 """ 658 659 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
661 def get_x_mode(self, xio : int) -> int: 662 """ 663 get the kind of output that the XIO outputs will give (see register `SPC_DDS_X0_MODE` in the manual) 664 665 Parameters 666 ---------- 667 xio : int 668 the XIO channel number 669 670 Returns 671 ------- 672 int 673 the mode that the channel needs to run in 674 SPC_DDS_XIO_SEQUENCE = 0 675 turn on and off the XIO channels using commands in the DDS cmd queue 676 SPC_DDS_XIO_ARM = 1 677 when the DDS firmware is waiting for a trigger to come this signal is high 678 SPC_DDS_XIO_LATCH = 2 679 when the DDS firmware starts executing a change this signal is high 680 """ 681 682 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
684 def freq_ramp_stepsize(self, divider : int) -> None: 685 """ 686 number of timesteps before the frequency is changed during a frequency ramp. (see register `SPC_DDS_FREQ_RAMP_STEPSIZE` in the manual) 687 688 NOTES 689 ----- 690 - this is a global setting for all cores 691 - 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 692 693 Parameters 694 ---------- 695 divider : int 696 the number of DDS timesteps that a value is kept constant during a frequency ramp 697 """ 698 699 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
701 def get_freq_ramp_stepsize(self) -> int: 702 """ 703 get the number of timesteps before the frequency is changed during a frequency ramp. (see register `SPC_DDS_FREQ_RAMP_STEPSIZE` in the manual) 704 705 NOTES 706 ----- 707 - this is a global setting for all cores 708 - 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 709 710 Returns 711 ---------- 712 divider : int 713 the number of DDS timesteps that a value is kept constant during a frequency ramp 714 """ 715 716 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
718 def amp_ramp_stepsize(self, divider : int) -> None: 719 """ 720 number of timesteps before the amplitude is changed during a frequency ramp. (see register `SPC_DDS_AMP_RAMP_STEPSIZE` in the manual) 721 722 NOTES 723 ----- 724 - this is a global setting for all cores 725 - internally the time divider is used to calculate the amount of change per event using a given amplitude slope, 726 please set the time divider before setting the amplitude slope 727 728 Parameters 729 ---------- 730 divider : int 731 the number of DDS timesteps that a value is kept constant during an amplitude ramp 732 """ 733 734 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
736 def get_amp_ramp_stepsize(self) -> int: 737 """ 738 get the number of timesteps before the amplitude is changed during a frequency ramp. (see register `SPC_DDS_AMP_RAMP_STEPSIZE` in the manual) 739 740 NOTES 741 ----- 742 - this is a global setting for all cores 743 - internally the time divider is used to calculate the amount of change per event using a given amplitude slope, 744 please set the time divider before setting the amplitude slope 745 746 Returns 747 ---------- 748 divider : int 749 the number of DDS timesteps that a value is kept constant during an amplitude ramp 750 """ 751 752 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
756 def amp(self, *args) -> None: 757 """ 758 set the amplitude of the sine wave of a specific core (see register `SPC_DDS_CORE0_AMP` in the manual) 759 760 Parameters 761 ---------- 762 core_index : int (optional) 763 the index of the core to be changed 764 amplitude : float 765 the value between 0 and 1 corresponding to the amplitude 766 """ 767 768 if len(args) == 1: 769 amplitude = args[0] 770 for core in self.cores: 771 core.amp(amplitude) 772 elif len(args) == 2: 773 core_index, amplitude = args 774 self.cores[core_index].amp(amplitude) 775 else: 776 raise TypeError("amp() takes 1 or 2 positional arguments ({} given)".format(len(args) + 1)) 777 # 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
756 def amp(self, *args) -> None: 757 """ 758 set the amplitude of the sine wave of a specific core (see register `SPC_DDS_CORE0_AMP` in the manual) 759 760 Parameters 761 ---------- 762 core_index : int (optional) 763 the index of the core to be changed 764 amplitude : float 765 the value between 0 and 1 corresponding to the amplitude 766 """ 767 768 if len(args) == 1: 769 amplitude = args[0] 770 for core in self.cores: 771 core.amp(amplitude) 772 elif len(args) == 2: 773 core_index, amplitude = args 774 self.cores[core_index].amp(amplitude) 775 else: 776 raise TypeError("amp() takes 1 or 2 positional arguments ({} given)".format(len(args) + 1)) 777 # 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
781 def get_amp(self, core_index : int, return_unit = None) -> float: 782 """ 783 gets the amplitude of the sine wave of a specific core (see register `SPC_DDS_CORE0_AMP` in the manual) 784 785 Parameters 786 ---------- 787 core_index : int 788 the index of the core to be changed 789 return_unit : pint.Unit = None 790 the unit of the returned amplitude, by default None 791 792 Returns 793 ------- 794 float | pint.Quantity 795 the value between 0 and 1 corresponding to the amplitude of the specific core or in the specified unit 796 """ 797 798 return self.cores[core_index].get_amp(return_unit) 799 # 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
781 def get_amp(self, core_index : int, return_unit = None) -> float: 782 """ 783 gets the amplitude of the sine wave of a specific core (see register `SPC_DDS_CORE0_AMP` in the manual) 784 785 Parameters 786 ---------- 787 core_index : int 788 the index of the core to be changed 789 return_unit : pint.Unit = None 790 the unit of the returned amplitude, by default None 791 792 Returns 793 ------- 794 float | pint.Quantity 795 the value between 0 and 1 corresponding to the amplitude of the specific core or in the specified unit 796 """ 797 798 return self.cores[core_index].get_amp(return_unit) 799 # 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
803 def avail_amp_min(self) -> float: 804 """ 805 get the minimum available amplitude (see register `SPC_DDS_AVAIL_AMP_MIN` in the manual) 806 807 Returns 808 ------- 809 float 810 the minimum available amplitude 811 812 TODO: unitize! 813 """ 814 815 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!):
817 def avail_amp_max(self) -> float: 818 """ 819 get the maximum available amplitude (see register `SPC_DDS_AVAIL_AMP_MAX` in the manual) 820 821 Returns 822 ------- 823 float 824 the maximum available amplitude 825 826 TODO: unitize! 827 """ 828 829 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!):
831 def avail_amp_step(self) -> float: 832 """ 833 get the step size of the available amplitudes (see register `SPC_DDS_AVAIL_AMP_STEP` in the manual) 834 835 Returns 836 ------- 837 float 838 the step size of the available amplitudes 839 840 TODO: unitize! 841 """ 842 843 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!):
846 def freq(self, *args) -> None: 847 """ 848 set the frequency of the sine wave of a specific core (see register `SPC_DDS_CORE0_FREQ` in the manual) 849 850 Parameters 851 ---------- 852 core_index : int (optional) 853 the index of the core to be changed 854 frequency : float 855 the value of the frequency in Hz 856 """ 857 858 if len(args) == 1: 859 frequency = args[0] 860 for core in self.cores: 861 core.freq(frequency) 862 elif len(args) == 2: 863 core_index, frequency = args 864 self.cores[core_index].freq(frequency) 865 else: 866 raise TypeError("freq() takes 1 or 2 positional arguments ({} given)".format(len(args) + 1)) 867 # 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
846 def freq(self, *args) -> None: 847 """ 848 set the frequency of the sine wave of a specific core (see register `SPC_DDS_CORE0_FREQ` in the manual) 849 850 Parameters 851 ---------- 852 core_index : int (optional) 853 the index of the core to be changed 854 frequency : float 855 the value of the frequency in Hz 856 """ 857 858 if len(args) == 1: 859 frequency = args[0] 860 for core in self.cores: 861 core.freq(frequency) 862 elif len(args) == 2: 863 core_index, frequency = args 864 self.cores[core_index].freq(frequency) 865 else: 866 raise TypeError("freq() takes 1 or 2 positional arguments ({} given)".format(len(args) + 1)) 867 # 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
871 def get_freq(self, core_index : int, return_unit = None) -> float: 872 """ 873 gets the frequency of the sine wave of a specific core (see register `SPC_DDS_CORE0_FREQ` in the manual) 874 875 Parameters 876 ---------- 877 core_index : int 878 the index of the core to be changed 879 return_unit : pint.Unit = None 880 the unit of the returned frequency, by default None 881 882 Returns 883 ------- 884 float | pint.Quantity 885 the value of the frequency in Hz the specific core or in the specified unit 886 """ 887 888 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
871 def get_freq(self, core_index : int, return_unit = None) -> float: 872 """ 873 gets the frequency of the sine wave of a specific core (see register `SPC_DDS_CORE0_FREQ` in the manual) 874 875 Parameters 876 ---------- 877 core_index : int 878 the index of the core to be changed 879 return_unit : pint.Unit = None 880 the unit of the returned frequency, by default None 881 882 Returns 883 ------- 884 float | pint.Quantity 885 the value of the frequency in Hz the specific core or in the specified unit 886 """ 887 888 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
892 def avail_freq_min(self) -> float: 893 """ 894 get the minimum available frequency (see register `SPC_DDS_AVAIL_FREQ_MIN` in the manual) 895 896 Returns 897 ------- 898 float 899 the minimum available frequency 900 901 TODO: unitize! 902 """ 903 904 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!):
906 def avail_freq_max(self) -> float: 907 """ 908 get the maximum available frequency (see register `SPC_DDS_AVAIL_FREQ_MAX` in the manual) 909 910 Returns 911 ------- 912 float 913 the maximum available frequency 914 915 TODO: unitize! 916 """ 917 918 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!):
920 def avail_freq_step(self) -> float: 921 """ 922 get the step size of the available frequencies (see register `SPC_DDS_AVAIL_FREQ_STEP` in the manual) 923 924 Returns 925 ------- 926 float 927 the step size of the available frequencies 928 929 TODO: unitize! 930 """ 931 932 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!):
935 def phase(self, *args) -> None: 936 """ 937 set the phase of the sine wave of a specific core (see register `SPC_DDS_CORE0_PHASE` in the manual) 938 939 Parameters 940 ---------- 941 core_index : int (optional) 942 the index of the core to be changed 943 phase : float 944 the value between 0 and 360 degrees of the phase 945 """ 946 947 if len(args) == 1: 948 phase = args[0] 949 for core in self.cores: 950 core.phase(phase) 951 elif len(args) == 2: 952 core_index, phase = args 953 self.cores[core_index].phase(phase) 954 else: 955 raise TypeError("phase() takes 1 or 2 positional arguments ({} given)".format(len(args) + 1)) 956 # 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
958 def get_phase(self, core_index : int, return_unit = None) -> float: 959 """ 960 gets the phase of the sine wave of a specific core (see register `SPC_DDS_CORE0_PHASE` in the manual) 961 962 Parameters 963 ---------- 964 core_index : int 965 the index of the core to be changed 966 return_unit : pint.Unit = None 967 the unit of the returned phase, by default None 968 969 Returns 970 ------- 971 float 972 the value between 0 and 360 degrees of the phase 973 """ 974 975 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
977 def avail_phase_min(self) -> float: 978 """ 979 get the minimum available phase (see register `SPC_DDS_AVAIL_PHASE_MIN` in the manual) 980 981 Returns 982 ------- 983 float 984 the minimum available phase 985 986 TODO: unitize! 987 """ 988 989 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!):
991 def avail_phase_max(self) -> float: 992 """ 993 get the maximum available phase (see register `SPC_DDS_AVAIL_PHASE_MAX` in the manual) 994 995 Returns 996 ------- 997 float 998 the maximum available phase 999 1000 TODO: unitize! 1001 """ 1002 1003 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!):
1005 def avail_phase_step(self) -> float: 1006 """ 1007 get the step size of the available phases (see register `SPC_DDS_AVAIL_PHASE_STEP` in the manual) 1008 1009 Returns 1010 ------- 1011 float 1012 the step size of the available phases 1013 1014 TODO: unitize! 1015 """ 1016 1017 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!):
1019 def x_manual_output(self, state_mask : int) -> None: 1020 """ 1021 set the output of the xio channels using a bit mask (see register `SPC_DDS_X_MANUAL_OUTPUT` in the manual) 1022 1023 Parameters 1024 ---------- 1025 state_mask : int 1026 bit mask where the bits correspond to specific channels and 1 to on and 0 to off. 1027 """ 1028 1029 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.
1031 def get_x_manual_output(self) -> int: 1032 """ 1033 get the output of the xio channels using a bit mask (see register `SPC_DDS_X_MANUAL_OUTPUT` in the manual) 1034 1035 Returns 1036 ---------- 1037 int 1038 bit mask where the bits correspond to specific channels and 1 to on and 0 to off. 1039 """ 1040 1041 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.
1045 def freq_slope(self, *args) -> None: 1046 """ 1047 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) 1048 1049 Parameters 1050 ---------- 1051 core_index : int (optional) 1052 the index of the core to be changed 1053 slope : float 1054 the rate of frequency change in Hz/s 1055 """ 1056 1057 if len(args) == 1: 1058 slope = args[0] 1059 for core in self.cores: 1060 core.freq_slope(slope) 1061 elif len(args) == 2: 1062 core_index, slope = args 1063 self.cores[core_index].freq_slope(slope) 1064 else: 1065 raise TypeError("freq_slope() takes 1 or 2 positional arguments ({} given)".format(len(args) + 1)) 1066 # 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
1045 def freq_slope(self, *args) -> None: 1046 """ 1047 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) 1048 1049 Parameters 1050 ---------- 1051 core_index : int (optional) 1052 the index of the core to be changed 1053 slope : float 1054 the rate of frequency change in Hz/s 1055 """ 1056 1057 if len(args) == 1: 1058 slope = args[0] 1059 for core in self.cores: 1060 core.freq_slope(slope) 1061 elif len(args) == 2: 1062 core_index, slope = args 1063 self.cores[core_index].freq_slope(slope) 1064 else: 1065 raise TypeError("freq_slope() takes 1 or 2 positional arguments ({} given)".format(len(args) + 1)) 1066 # 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
1070 def get_freq_slope(self, core_index : int, return_unit=None) -> float: 1071 """ 1072 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) 1073 1074 Parameters 1075 ---------- 1076 core_index : int 1077 the index of the core to be changed 1078 return_unit : pint.Unit = None 1079 the unit of the returned frequency slope, by default None 1080 1081 Returns 1082 ------- 1083 float 1084 the rate of frequency change in Hz/s 1085 """ 1086 1087 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
1070 def get_freq_slope(self, core_index : int, return_unit=None) -> float: 1071 """ 1072 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) 1073 1074 Parameters 1075 ---------- 1076 core_index : int 1077 the index of the core to be changed 1078 return_unit : pint.Unit = None 1079 the unit of the returned frequency slope, by default None 1080 1081 Returns 1082 ------- 1083 float 1084 the rate of frequency change in Hz/s 1085 """ 1086 1087 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
1091 def avail_freq_slope_min(self) -> float: 1092 """ 1093 get the minimum available frequency slope (see register `SPC_DDS_AVAIL_FREQ_SLOPE_MIN` in the manual) 1094 1095 Returns 1096 ------- 1097 float 1098 the minimum available frequency slope 1099 1100 TODO: unitize! 1101 """ 1102 1103 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!):
1105 def avail_freq_slope_max(self) -> float: 1106 """ 1107 get the maximum available frequency slope (see register `SPC_DDS_AVAIL_FREQ_SLOPE_MAX` in the manual) 1108 1109 Returns 1110 ------- 1111 float 1112 the maximum available frequency slope 1113 1114 TODO: unitize! 1115 """ 1116 1117 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!):
1119 def avail_freq_slope_step(self) -> float: 1120 """ 1121 get the step size of the available frequency slopes (see register `SPC_DDS_AVAIL_FREQ_SLOPE_STEP` in the manual) 1122 1123 Returns 1124 ------- 1125 float 1126 the step size of the available frequency slopes 1127 1128 TODO: unitize! 1129 """ 1130 1131 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!):
1134 def amp_slope(self, *args) -> None: 1135 """ 1136 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) 1137 1138 Parameters 1139 ---------- 1140 core_index : int (optional) 1141 the index of the core to be changed 1142 slope : float 1143 the rate of amplitude change in 1/s 1144 """ 1145 1146 if len(args) == 1: 1147 slope = args[0] 1148 for core in self.cores: 1149 core.amp_slope(slope) 1150 elif len(args) == 2: 1151 core_index, slope = args 1152 self.cores[core_index].amp_slope(slope) 1153 else: 1154 raise TypeError("amp_slope() takes 1 or 2 positional arguments ({} given)".format(len(args) + 1)) 1155 # 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
1134 def amp_slope(self, *args) -> None: 1135 """ 1136 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) 1137 1138 Parameters 1139 ---------- 1140 core_index : int (optional) 1141 the index of the core to be changed 1142 slope : float 1143 the rate of amplitude change in 1/s 1144 """ 1145 1146 if len(args) == 1: 1147 slope = args[0] 1148 for core in self.cores: 1149 core.amp_slope(slope) 1150 elif len(args) == 2: 1151 core_index, slope = args 1152 self.cores[core_index].amp_slope(slope) 1153 else: 1154 raise TypeError("amp_slope() takes 1 or 2 positional arguments ({} given)".format(len(args) + 1)) 1155 # 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
1159 def get_amp_slope(self, core_index : int, return_unit = None) -> float: 1160 """ 1161 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) 1162 1163 Parameters 1164 ---------- 1165 core_index : int 1166 the index of the core to be changed 1167 return_unit : pint.Unit = None 1168 the unit of the returned amplitude slope, by default None 1169 1170 Returns 1171 ------- 1172 float 1173 the rate of amplitude change in 1/s 1174 """ 1175 1176 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
1180 def avail_amp_slope_min(self) -> float: 1181 """ 1182 get the minimum available amplitude slope (see register `SPC_DDS_AVAIL_AMP_SLOPE_MIN` in the manual) 1183 1184 Returns 1185 ------- 1186 float 1187 the minimum available amplitude slope 1188 1189 TODO: unitize! 1190 """ 1191 1192 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!):
1194 def avail_amp_slope_max(self) -> float: 1195 """ 1196 get the maximum available amplitude slope (see register `SPC_DDS_AVAIL_AMP_SLOPE_MAX` in the manual) 1197 1198 Returns 1199 ------- 1200 float 1201 the maximum available amplitude slope 1202 1203 TODO: unitize! 1204 """ 1205 1206 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!):
1208 def avail_amp_slope_step(self) -> float: 1209 """ 1210 get the step size of the available amplitude slopes (see register `SPC_DDS_AVAIL_AMP_SLOPE_STEP` in the manual) 1211 1212 Returns 1213 ------- 1214 float 1215 the step size of the available amplitude slopes 1216 1217 TODO: unitize! 1218 """ 1219 1220 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!):
1223 def cmd(self, command : int) -> None: 1224 """ 1225 execute a DDS specific control flow command (see register `SPC_DDS_CMD` in the manual) 1226 1227 Parameters 1228 ---------- 1229 command : int 1230 DDS specific command 1231 """ 1232 1233 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
1235 def exec_at_trg(self) -> None: 1236 """ 1237 execute the commands in the shadow register at the next trigger event (see register `SPC_DDS_CMD` in the manual) 1238 """ 1239 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)
1235 def exec_at_trg(self) -> None: 1236 """ 1237 execute the commands in the shadow register at the next trigger event (see register `SPC_DDS_CMD` in the manual) 1238 """ 1239 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)
1235 def exec_at_trg(self) -> None: 1236 """ 1237 execute the commands in the shadow register at the next trigger event (see register `SPC_DDS_CMD` in the manual) 1238 """ 1239 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)
1244 def exec_now(self) -> None: 1245 """ 1246 execute the commands in the shadow register as soon as possible (see register `SPC_DDS_CMD` in the manual) 1247 """ 1248 1249 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)
1244 def exec_now(self) -> None: 1245 """ 1246 execute the commands in the shadow register as soon as possible (see register `SPC_DDS_CMD` in the manual) 1247 """ 1248 1249 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)
1253 def trg_count(self) -> int: 1254 """ 1255 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) 1256 1257 Returns 1258 ------- 1259 int 1260 the number of trigger exec_at_trg and exec_now command that have been executed 1261 """ 1262 1263 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
1265 def write_to_card(self, flags=0) -> None: 1266 """ 1267 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) 1268 1269 Parameters 1270 ---------- 1271 flags : int = 0 1272 the flags that can be set with the write_to_card command 1273 """ 1274 1275 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
1278 def kwargs2mask(self, kwargs : dict[str, bool], prefix : str = "") -> int: 1279 """ 1280 DDS helper: transform a dictionary with keys with a specific prefix to a bitmask 1281 1282 Parameters 1283 ---------- 1284 kwargs : dict 1285 dictonary with keys with a specific prefix and values given by bools 1286 prefix : str 1287 a prefix for the key names 1288 1289 Returns 1290 ------- 1291 int 1292 bit mask 1293 1294 Example 1295 ------- 1296 ['core_0' = True, 'core_2' = False, 'core_3' = True] => 0b1001 = 9 1297 """ 1298 1299 mask = 0 1300 for keyword, value in kwargs.items(): 1301 bit = int(keyword[len(prefix):]) 1302 if value: 1303 mask |= 1 << bit 1304 else: 1305 mask &= ~(1 << bit) 1306 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
1278 def kwargs2mask(self, kwargs : dict[str, bool], prefix : str = "") -> int: 1279 """ 1280 DDS helper: transform a dictionary with keys with a specific prefix to a bitmask 1281 1282 Parameters 1283 ---------- 1284 kwargs : dict 1285 dictonary with keys with a specific prefix and values given by bools 1286 prefix : str 1287 a prefix for the key names 1288 1289 Returns 1290 ------- 1291 int 1292 bit mask 1293 1294 Example 1295 ------- 1296 ['core_0' = True, 'core_2' = False, 'core_3' = True] => 0b1001 = 9 1297 """ 1298 1299 mask = 0 1300 for keyword, value in kwargs.items(): 1301 bit = int(keyword[len(prefix):]) 1302 if value: 1303 mask |= 1 << bit 1304 else: 1305 mask &= ~(1 << bit) 1306 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
13class DDSCore: 14 """ 15 a class for controlling a single DDS core 16 """ 17 18 dds : "DDS" 19 index : int 20 channel : Channel 21 22 def __init__(self, core_index, dds, *args, **kwargs) -> None: 23 self.dds = dds 24 self.index = core_index 25 self.channel = kwargs.get("channel", None) 26 27 def __int__(self) -> int: 28 """ 29 get the index of the core 30 31 Returns 32 ------- 33 int 34 the index of the core 35 """ 36 return self.index 37 __index__ = __int__ 38 39 def __str__(self) -> str: 40 """ 41 get the string representation of the core 42 43 Returns 44 ------- 45 str 46 the string representation of the core 47 """ 48 return f"Core {self.index}" 49 __repr__ = __str__ 50 51 def __add__(self, other) -> int: 52 """ 53 add the index of the core to another index 54 55 Parameters 56 ---------- 57 other : int 58 the other index 59 60 Returns 61 ------- 62 int 63 the sum of the two indices 64 """ 65 return self.index + other 66 67 # DDS "static" parameters 68 def amp(self, amplitude : float) -> None: 69 """ 70 set the amplitude of the sine wave of a specific core (see register `SPC_DDS_CORE0_AMP` in the manual) 71 72 Parameters 73 ---------- 74 amplitude : float | pint.Quantity 75 the value between 0 and 1 corresponding to the amplitude 76 """ 77 78 if self.channel is not None: 79 amplitude = self.channel.to_amplitude_fraction(amplitude) 80 elif isinstance(amplitude, units.Quantity) and amplitude.check("[]"): 81 amplitude = UnitConversion.convert(amplitude, units.fraction, float, rounding=None) 82 self.dds.set_d(SPC_DDS_CORE0_AMP + self.index, float(amplitude)) 83 # aliases 84 amplitude = amp 85 86 def get_amp(self, return_unit = None) -> float: 87 """ 88 gets the amplitude of the sine wave of a specific core (see register `SPC_DDS_CORE0_AMP` in the manual) 89 90 Parameters 91 ---------- 92 return_unit : pint.Unit = None 93 the unit of the returned amplitude, by default None 94 95 Returns 96 ------- 97 float 98 the value between 0 and 1 corresponding to the amplitude 99 """ 100 101 return_value = self.dds.card.get_d(SPC_DDS_CORE0_AMP + self.index) 102 if self.channel is not None: 103 return_value = self.channel.from_amplitude_fraction(return_value, return_unit) 104 else: 105 return_value = UnitConversion.to_unit(return_value, return_unit) 106 return return_value 107 # aliases 108 get_amplitude = get_amp 109 110 def freq(self, frequency : float) -> None: 111 """ 112 set the frequency of the sine wave of a specific core (see register `SPC_DDS_CORE0_FREQ` in the manual) 113 114 Parameters 115 ---------- 116 frequency : float | pint.Quantity 117 the value of the frequency in Hz 118 """ 119 120 frequency = UnitConversion.convert(frequency, units.Hz, float, rounding=None) 121 self.dds.set_d(SPC_DDS_CORE0_FREQ + self.index, float(frequency)) 122 # aliases 123 frequency = freq 124 125 def get_freq(self, return_unit = None) -> float: 126 """ 127 gets the frequency of the sine wave of a specific core (see register `SPC_DDS_CORE0_FREQ` in the manual) 128 129 Parameters 130 ---------- 131 return_unit : pint.Unit = None 132 the unit of the returned frequency, by default None 133 134 Returns 135 ------- 136 float | pint.Quantity 137 the value of the frequency in Hz the specific core 138 """ 139 140 return_value = self.dds.card.get_d(SPC_DDS_CORE0_FREQ + self.index) 141 if return_unit is not None: return_value = UnitConversion.to_unit(return_value * units.Hz, return_unit) 142 return return_value 143 # aliases 144 get_frequency = get_freq 145 146 def phase(self, phase : float) -> None: 147 """ 148 set the phase of the sine wave of a specific core (see register `SPC_DDS_CORE0_PHASE` in the manual) 149 150 Parameters 151 ---------- 152 phase : float | pint.Quantity 153 the value between 0 and 360 degrees of the phase 154 """ 155 156 phase = UnitConversion.convert(phase, units.deg, float, rounding=None) 157 self.dds.set_d(SPC_DDS_CORE0_PHASE + self.index, float(phase)) 158 159 def get_phase(self, return_unit = None) -> float: 160 """ 161 gets the phase of the sine wave of a specific core (see register `SPC_DDS_CORE0_PHASE` in the manual) 162 163 Returns 164 ------- 165 float 166 the value between 0 and 360 degrees of the phase 167 """ 168 169 return_value = self.dds.card.get_d(SPC_DDS_CORE0_PHASE + self.index) 170 if return_unit is not None: return_value = UnitConversion.to_unit(return_value * units.deg, return_unit) 171 return return_value 172 173 # DDS dynamic parameters 174 def freq_slope(self, slope : float) -> None: 175 """ 176 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) 177 178 Parameters 179 ---------- 180 slope : float | pint.Quantity 181 the rate of frequency change in Hz/s (positive or negative) or specified unit 182 """ 183 184 slope = UnitConversion.convert(slope, units.Hz/units.s, float, rounding=None) 185 self.dds.set_d(SPC_DDS_CORE0_FREQ_SLOPE + self.index, float(slope)) 186 # aliases 187 frequency_slope = freq_slope 188 189 def get_freq_slope(self, return_unit = None) -> float: 190 """ 191 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) 192 193 Parameters 194 ---------- 195 return_unit : pint.Unit = None 196 the unit of the returned frequency slope, by default None 197 198 Returns 199 ------- 200 float 201 the rate of frequency change in Hz/s 202 """ 203 204 return_value = self.dds.card.get_d(SPC_DDS_CORE0_FREQ_SLOPE + self.index) 205 if return_unit is not None: return_value = UnitConversion.to_unit(return_value * units.Hz/units.s, return_unit) 206 return return_value 207 # aliases 208 get_frequency_slope = get_freq_slope 209 210 def amp_slope(self, slope : float) -> None: 211 """ 212 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) 213 214 Parameters 215 ---------- 216 slope : float | pint.Quantity 217 the rate of amplitude change in 1/s (positive or negative) or specified unit 218 """ 219 220 slope = UnitConversion.convert(slope, 1/units.s, float, rounding=None) 221 self.dds.set_d(SPC_DDS_CORE0_AMP_SLOPE + self.index, float(slope)) 222 # aliases 223 amplitude_slope = amp_slope 224 225 def get_amp_slope(self, return_unit = None) -> float: 226 """ 227 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) 228 229 Parameters 230 ---------- 231 return_unit : pint.Unit = None 232 the unit of the returned amplitude slope, by default None 233 234 Returns 235 ------- 236 float 237 the rate of amplitude change in 1/s 238 """ 239 240 241 return_value = self.dds.card.get_d(SPC_DDS_CORE0_AMP_SLOPE + self.index) 242 if return_unit is not None: return_value = UnitConversion.to_unit(return_value / units.s, return_unit) 243 return return_value 244 # aliases 245 amplitude_slope = amp_slope
a class for controlling a single DDS core
68 def amp(self, amplitude : float) -> None: 69 """ 70 set the amplitude of the sine wave of a specific core (see register `SPC_DDS_CORE0_AMP` in the manual) 71 72 Parameters 73 ---------- 74 amplitude : float | pint.Quantity 75 the value between 0 and 1 corresponding to the amplitude 76 """ 77 78 if self.channel is not None: 79 amplitude = self.channel.to_amplitude_fraction(amplitude) 80 elif isinstance(amplitude, units.Quantity) and amplitude.check("[]"): 81 amplitude = UnitConversion.convert(amplitude, units.fraction, float, rounding=None) 82 self.dds.set_d(SPC_DDS_CORE0_AMP + self.index, float(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
68 def amp(self, amplitude : float) -> None: 69 """ 70 set the amplitude of the sine wave of a specific core (see register `SPC_DDS_CORE0_AMP` in the manual) 71 72 Parameters 73 ---------- 74 amplitude : float | pint.Quantity 75 the value between 0 and 1 corresponding to the amplitude 76 """ 77 78 if self.channel is not None: 79 amplitude = self.channel.to_amplitude_fraction(amplitude) 80 elif isinstance(amplitude, units.Quantity) and amplitude.check("[]"): 81 amplitude = UnitConversion.convert(amplitude, units.fraction, float, rounding=None) 82 self.dds.set_d(SPC_DDS_CORE0_AMP + self.index, float(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
86 def get_amp(self, return_unit = None) -> float: 87 """ 88 gets the amplitude of the sine wave of a specific core (see register `SPC_DDS_CORE0_AMP` in the manual) 89 90 Parameters 91 ---------- 92 return_unit : pint.Unit = None 93 the unit of the returned amplitude, by default None 94 95 Returns 96 ------- 97 float 98 the value between 0 and 1 corresponding to the amplitude 99 """ 100 101 return_value = self.dds.card.get_d(SPC_DDS_CORE0_AMP + self.index) 102 if self.channel is not None: 103 return_value = self.channel.from_amplitude_fraction(return_value, return_unit) 104 else: 105 return_value = UnitConversion.to_unit(return_value, return_unit) 106 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
86 def get_amp(self, return_unit = None) -> float: 87 """ 88 gets the amplitude of the sine wave of a specific core (see register `SPC_DDS_CORE0_AMP` in the manual) 89 90 Parameters 91 ---------- 92 return_unit : pint.Unit = None 93 the unit of the returned amplitude, by default None 94 95 Returns 96 ------- 97 float 98 the value between 0 and 1 corresponding to the amplitude 99 """ 100 101 return_value = self.dds.card.get_d(SPC_DDS_CORE0_AMP + self.index) 102 if self.channel is not None: 103 return_value = self.channel.from_amplitude_fraction(return_value, return_unit) 104 else: 105 return_value = UnitConversion.to_unit(return_value, return_unit) 106 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
110 def freq(self, frequency : float) -> None: 111 """ 112 set the frequency of the sine wave of a specific core (see register `SPC_DDS_CORE0_FREQ` in the manual) 113 114 Parameters 115 ---------- 116 frequency : float | pint.Quantity 117 the value of the frequency in Hz 118 """ 119 120 frequency = UnitConversion.convert(frequency, units.Hz, float, rounding=None) 121 self.dds.set_d(SPC_DDS_CORE0_FREQ + self.index, float(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
110 def freq(self, frequency : float) -> None: 111 """ 112 set the frequency of the sine wave of a specific core (see register `SPC_DDS_CORE0_FREQ` in the manual) 113 114 Parameters 115 ---------- 116 frequency : float | pint.Quantity 117 the value of the frequency in Hz 118 """ 119 120 frequency = UnitConversion.convert(frequency, units.Hz, float, rounding=None) 121 self.dds.set_d(SPC_DDS_CORE0_FREQ + self.index, float(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
125 def get_freq(self, return_unit = None) -> float: 126 """ 127 gets the frequency of the sine wave of a specific core (see register `SPC_DDS_CORE0_FREQ` in the manual) 128 129 Parameters 130 ---------- 131 return_unit : pint.Unit = None 132 the unit of the returned frequency, by default None 133 134 Returns 135 ------- 136 float | pint.Quantity 137 the value of the frequency in Hz the specific core 138 """ 139 140 return_value = self.dds.card.get_d(SPC_DDS_CORE0_FREQ + self.index) 141 if return_unit is not None: return_value = UnitConversion.to_unit(return_value * units.Hz, return_unit) 142 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
125 def get_freq(self, return_unit = None) -> float: 126 """ 127 gets the frequency of the sine wave of a specific core (see register `SPC_DDS_CORE0_FREQ` in the manual) 128 129 Parameters 130 ---------- 131 return_unit : pint.Unit = None 132 the unit of the returned frequency, by default None 133 134 Returns 135 ------- 136 float | pint.Quantity 137 the value of the frequency in Hz the specific core 138 """ 139 140 return_value = self.dds.card.get_d(SPC_DDS_CORE0_FREQ + self.index) 141 if return_unit is not None: return_value = UnitConversion.to_unit(return_value * units.Hz, return_unit) 142 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
146 def phase(self, phase : float) -> None: 147 """ 148 set the phase of the sine wave of a specific core (see register `SPC_DDS_CORE0_PHASE` in the manual) 149 150 Parameters 151 ---------- 152 phase : float | pint.Quantity 153 the value between 0 and 360 degrees of the phase 154 """ 155 156 phase = UnitConversion.convert(phase, units.deg, float, rounding=None) 157 self.dds.set_d(SPC_DDS_CORE0_PHASE + self.index, float(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
159 def get_phase(self, return_unit = None) -> float: 160 """ 161 gets the phase of the sine wave of a specific core (see register `SPC_DDS_CORE0_PHASE` in the manual) 162 163 Returns 164 ------- 165 float 166 the value between 0 and 360 degrees of the phase 167 """ 168 169 return_value = self.dds.card.get_d(SPC_DDS_CORE0_PHASE + self.index) 170 if return_unit is not None: return_value = UnitConversion.to_unit(return_value * units.deg, return_unit) 171 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
174 def freq_slope(self, slope : float) -> None: 175 """ 176 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) 177 178 Parameters 179 ---------- 180 slope : float | pint.Quantity 181 the rate of frequency change in Hz/s (positive or negative) or specified unit 182 """ 183 184 slope = UnitConversion.convert(slope, units.Hz/units.s, float, rounding=None) 185 self.dds.set_d(SPC_DDS_CORE0_FREQ_SLOPE + self.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
- slope (float | pint.Quantity): the rate of frequency change in Hz/s (positive or negative) or specified unit
174 def freq_slope(self, slope : float) -> None: 175 """ 176 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) 177 178 Parameters 179 ---------- 180 slope : float | pint.Quantity 181 the rate of frequency change in Hz/s (positive or negative) or specified unit 182 """ 183 184 slope = UnitConversion.convert(slope, units.Hz/units.s, float, rounding=None) 185 self.dds.set_d(SPC_DDS_CORE0_FREQ_SLOPE + self.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
- slope (float | pint.Quantity): the rate of frequency change in Hz/s (positive or negative) or specified unit
189 def get_freq_slope(self, return_unit = None) -> float: 190 """ 191 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) 192 193 Parameters 194 ---------- 195 return_unit : pint.Unit = None 196 the unit of the returned frequency slope, by default None 197 198 Returns 199 ------- 200 float 201 the rate of frequency change in Hz/s 202 """ 203 204 return_value = self.dds.card.get_d(SPC_DDS_CORE0_FREQ_SLOPE + self.index) 205 if return_unit is not None: return_value = UnitConversion.to_unit(return_value * units.Hz/units.s, return_unit) 206 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
189 def get_freq_slope(self, return_unit = None) -> float: 190 """ 191 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) 192 193 Parameters 194 ---------- 195 return_unit : pint.Unit = None 196 the unit of the returned frequency slope, by default None 197 198 Returns 199 ------- 200 float 201 the rate of frequency change in Hz/s 202 """ 203 204 return_value = self.dds.card.get_d(SPC_DDS_CORE0_FREQ_SLOPE + self.index) 205 if return_unit is not None: return_value = UnitConversion.to_unit(return_value * units.Hz/units.s, return_unit) 206 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
210 def amp_slope(self, slope : float) -> None: 211 """ 212 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) 213 214 Parameters 215 ---------- 216 slope : float | pint.Quantity 217 the rate of amplitude change in 1/s (positive or negative) or specified unit 218 """ 219 220 slope = UnitConversion.convert(slope, 1/units.s, float, rounding=None) 221 self.dds.set_d(SPC_DDS_CORE0_AMP_SLOPE + self.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
- slope (float | pint.Quantity): the rate of amplitude change in 1/s (positive or negative) or specified unit
210 def amp_slope(self, slope : float) -> None: 211 """ 212 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) 213 214 Parameters 215 ---------- 216 slope : float | pint.Quantity 217 the rate of amplitude change in 1/s (positive or negative) or specified unit 218 """ 219 220 slope = UnitConversion.convert(slope, 1/units.s, float, rounding=None) 221 self.dds.set_d(SPC_DDS_CORE0_AMP_SLOPE + self.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
- slope (float | pint.Quantity): the rate of amplitude change in 1/s (positive or negative) or specified unit
225 def get_amp_slope(self, return_unit = None) -> float: 226 """ 227 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) 228 229 Parameters 230 ---------- 231 return_unit : pint.Unit = None 232 the unit of the returned amplitude slope, by default None 233 234 Returns 235 ------- 236 float 237 the rate of amplitude change in 1/s 238 """ 239 240 241 return_value = self.dds.card.get_d(SPC_DDS_CORE0_AMP_SLOPE + self.index) 242 if return_unit is not None: return_value = UnitConversion.to_unit(return_value / units.s, return_unit) 243 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 30 def __init__(self, *args, **kwargs) -> None: 31 super().__init__(*args, **kwargs) 32 33 self.command_list = None 34 self.current_index = 0 35 36 self._dtm = SPCM_DDS_DTM_SINGLE 37 38 self.list_size = self.default_size() 39 40 def data_transfer_mode(self, mode : int) -> None: 41 """ 42 set the data transfer mode of the DDS 43 44 Parameters 45 ---------- 46 mode : int 47 the data transfer mode 48 """ 49 50 self._dtm = mode 51 self.card.set_i(SPC_DDS_DATA_TRANSFER_MODE, mode) 52 self.card.set_i(SPC_DDS_CMD, SPCM_DDS_CMD_WRITE_TO_CARD) 53 self.list_size = self.default_size() 54 55 def default_size(self) -> int: 56 """ 57 automatically determine the size of the commands list 58 """ 59 60 if self._dtm == SPCM_DDS_DTM_SINGLE: 61 return self.card.get_i(SPC_DDS_QUEUE_CMD_MAX) // 2 62 elif self._dtm == SPCM_DDS_DTM_DMA: 63 return KIBI(16) 64 raise SpcmException(text="Data transfer mode not supported.") 65 66 def allocate(self) -> None: 67 """ 68 allocate memory for the commands list 69 """ 70 if self.command_list is not None: 71 del self.command_list 72 elems = (ST_LIST_PARAM * (self._list_size + 1))() # +1 for the write to card command at the end 73 self.command_list = ctypes.cast(elems, ctypes.POINTER(ST_LIST_PARAM)) 74 self.current_index = 0 75 76 def load(self, data : dict, exec_mode : int = SPCM_DDS_CMD_EXEC_AT_TRG, repeat : int = 1) -> None: 77 """ 78 preload the command list with data 79 80 Parameters 81 ---------- 82 data : dict 83 the data to be preloaded 84 mode : int = SPCM_DDS_CMD_EXEC_AT_TRG 85 the mode of execution 86 repeat : int = 1 87 the number of times to repeat the data, if 0 is given the buffer is filled up with the maximal number of blocks that fit in. 88 89 TODO make this possible for multiple different keys 90 """ 91 92 key = list(data.keys())[0] 93 value_list = data[key] # For now only take the first key 94 size = len(value_list) 95 index = 0 96 if repeat == 0: 97 # repeat the data until the block is full 98 repeat = self.list_size // (2*size) 99 for _ in range(repeat): 100 for value in value_list: 101 # Write value 102 self.command_list[index].lReg = key 103 self.command_list[index].lType = TYPE_DOUBLE 104 self.command_list[index].dValue = value 105 index += 1 106 # Write trigger mode 107 self.command_list[index].lReg = SPC_DDS_CMD 108 self.command_list[index].lType = TYPE_INT64 109 self.command_list[index].llValue = exec_mode 110 index += 1 111 self.write_to_card() 112 self.current_index = index 113 114 def write_to_card(self) -> None: 115 """ 116 write the command list to the card 117 """ 118 119 self.command_list[self.current_index].lReg = SPC_DDS_CMD 120 self.command_list[self.current_index].lType = TYPE_INT64 121 self.command_list[self.current_index].llValue = SPCM_DDS_CMD_WRITE_TO_CARD 122 self.current_index += 1 123 124 def write(self) -> None: 125 """ 126 send the currently loaded data to the card 127 """ 128 129 if self.mode == self.WRITE_MODE.EXCEPTION_IF_FULL: 130 if self.avail_user_len() < (self.current_index) * ctypes.sizeof(ST_LIST_PARAM): 131 raise SpcmException(text="Buffer is full") 132 elif self.mode == self.WRITE_MODE.WAIT_IF_FULL: 133 timer = 0 134 while self.avail_user_len() < (self.current_index) * ctypes.sizeof(ST_LIST_PARAM): 135 print("Waiting for buffer to empty {}".format("."*(timer//100)), end="\r") 136 timer = (timer + 1) % 400 137 self.card.set_ptr(SPC_REGISTER_LIST, self.command_list, (self.current_index) * ctypes.sizeof(ST_LIST_PARAM)) 138 139 def avail_user_len(self) -> int: 140 """ 141 get the available space for commands in the hardware queue 142 143 TODO: check if this correct. Probably we should use fillsize_promille here 144 """ 145 146 if self._dtm == SPCM_DDS_DTM_SINGLE: 147 return self.list_size - self.card.get_i(SPC_DDS_QUEUE_CMD_COUNT) 148 elif self._dtm == SPCM_DDS_DTM_DMA: 149 return self.card.get_i(SPC_DATA_AVAIL_USER_LEN) 150 else: 151 raise SpcmException(text="Data transfer mode not supported.") 152 153 154 @property 155 def list_size(self) -> int: 156 """ 157 get the size of the command list 158 """ 159 160 return self._list_size 161 162 @list_size.setter 163 def list_size(self, size : int) -> None: 164 """ 165 set the size of the command list 166 167 Parameters 168 ---------- 169 size : int 170 the size of the command list 171 """ 172 173 self._list_size = size 174 self.allocate() 175 176 def reset(self) -> None: 177 """ 178 reset the dds firmware 179 """ 180 181 # The reset shouldn't be queued! 182 self.card.set_i(SPC_DDS_CMD, SPCM_DDS_CMD_RESET)
Abstraction of the set_ptr and register SPC_REGISTER_LIST
for command streaming in the DDS functionality
30 def __init__(self, *args, **kwargs) -> None: 31 super().__init__(*args, **kwargs) 32 33 self.command_list = None 34 self.current_index = 0 35 36 self._dtm = SPCM_DDS_DTM_SINGLE 37 38 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
154 @property 155 def list_size(self) -> int: 156 """ 157 get the size of the command list 158 """ 159 160 return self._list_size
get the size of the command list
40 def data_transfer_mode(self, mode : int) -> None: 41 """ 42 set the data transfer mode of the DDS 43 44 Parameters 45 ---------- 46 mode : int 47 the data transfer mode 48 """ 49 50 self._dtm = mode 51 self.card.set_i(SPC_DDS_DATA_TRANSFER_MODE, mode) 52 self.card.set_i(SPC_DDS_CMD, SPCM_DDS_CMD_WRITE_TO_CARD) 53 self.list_size = self.default_size()
set the data transfer mode of the DDS
Parameters
- mode (int): the data transfer mode
55 def default_size(self) -> int: 56 """ 57 automatically determine the size of the commands list 58 """ 59 60 if self._dtm == SPCM_DDS_DTM_SINGLE: 61 return self.card.get_i(SPC_DDS_QUEUE_CMD_MAX) // 2 62 elif self._dtm == SPCM_DDS_DTM_DMA: 63 return KIBI(16) 64 raise SpcmException(text="Data transfer mode not supported.")
automatically determine the size of the commands list
66 def allocate(self) -> None: 67 """ 68 allocate memory for the commands list 69 """ 70 if self.command_list is not None: 71 del self.command_list 72 elems = (ST_LIST_PARAM * (self._list_size + 1))() # +1 for the write to card command at the end 73 self.command_list = ctypes.cast(elems, ctypes.POINTER(ST_LIST_PARAM)) 74 self.current_index = 0
allocate memory for the commands list
76 def load(self, data : dict, exec_mode : int = SPCM_DDS_CMD_EXEC_AT_TRG, repeat : int = 1) -> None: 77 """ 78 preload the command list with data 79 80 Parameters 81 ---------- 82 data : dict 83 the data to be preloaded 84 mode : int = SPCM_DDS_CMD_EXEC_AT_TRG 85 the mode of execution 86 repeat : int = 1 87 the number of times to repeat the data, if 0 is given the buffer is filled up with the maximal number of blocks that fit in. 88 89 TODO make this possible for multiple different keys 90 """ 91 92 key = list(data.keys())[0] 93 value_list = data[key] # For now only take the first key 94 size = len(value_list) 95 index = 0 96 if repeat == 0: 97 # repeat the data until the block is full 98 repeat = self.list_size // (2*size) 99 for _ in range(repeat): 100 for value in value_list: 101 # Write value 102 self.command_list[index].lReg = key 103 self.command_list[index].lType = TYPE_DOUBLE 104 self.command_list[index].dValue = value 105 index += 1 106 # Write trigger mode 107 self.command_list[index].lReg = SPC_DDS_CMD 108 self.command_list[index].lType = TYPE_INT64 109 self.command_list[index].llValue = exec_mode 110 index += 1 111 self.write_to_card() 112 self.current_index = index
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
114 def write_to_card(self) -> None: 115 """ 116 write the command list to the card 117 """ 118 119 self.command_list[self.current_index].lReg = SPC_DDS_CMD 120 self.command_list[self.current_index].lType = TYPE_INT64 121 self.command_list[self.current_index].llValue = SPCM_DDS_CMD_WRITE_TO_CARD 122 self.current_index += 1
write the command list to the card
124 def write(self) -> None: 125 """ 126 send the currently loaded data to the card 127 """ 128 129 if self.mode == self.WRITE_MODE.EXCEPTION_IF_FULL: 130 if self.avail_user_len() < (self.current_index) * ctypes.sizeof(ST_LIST_PARAM): 131 raise SpcmException(text="Buffer is full") 132 elif self.mode == self.WRITE_MODE.WAIT_IF_FULL: 133 timer = 0 134 while self.avail_user_len() < (self.current_index) * ctypes.sizeof(ST_LIST_PARAM): 135 print("Waiting for buffer to empty {}".format("."*(timer//100)), end="\r") 136 timer = (timer + 1) % 400 137 self.card.set_ptr(SPC_REGISTER_LIST, self.command_list, (self.current_index) * ctypes.sizeof(ST_LIST_PARAM))
send the currently loaded data to the card
139 def avail_user_len(self) -> int: 140 """ 141 get the available space for commands in the hardware queue 142 143 TODO: check if this correct. Probably we should use fillsize_promille here 144 """ 145 146 if self._dtm == SPCM_DDS_DTM_SINGLE: 147 return self.list_size - self.card.get_i(SPC_DDS_QUEUE_CMD_COUNT) 148 elif self._dtm == SPCM_DDS_DTM_DMA: 149 return self.card.get_i(SPC_DATA_AVAIL_USER_LEN) 150 else: 151 raise SpcmException(text="Data transfer mode not supported.")
get the available space for commands in the hardware queue
TODO: check if this correct. Probably we should use fillsize_promille here
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
59 def write_to_card(self) -> None: 60 """ 61 write the current list of commands to the card 62 """ 63 64 super().write_to_card() 65 self.write()
write the current list of commands to the card
67 def write(self) -> None: 68 """ 69 write the current list of commands to the card and reset the current command index 70 """ 71 72 super().write() 73 self.current_index = 0
write the current list of commands to the card and reset the current command index
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)
set the amplitude of the sine wave of a specific core (see register SPC_DDS_CORE0_AMP
in the manual)
Parameters
- index (int): the core index
- amplitude (float): the value between 0 and 1 corresponding to the amplitude
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)
set the frequency of the sine wave of a specific core (see register SPC_DDS_CORE0_FREQ
in the manual)
Parameters
- index (int): the core index
- frequency (float): the value of the frequency in Hz
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)
set 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
- phase (float): the value between 0 and 360 degrees of the phase
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)
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): the index of the core to be changed
- slope (float): the rate of frequency change in Hz/s
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)
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
- slope (float): the rate of amplitude change in 1/s
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))
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): the time between DDS trigger events in seconds
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._pre_trigger = None 29 self._segment_size = 0 30 self._num_segments = 0 31 32 def segment_samples(self, segment_size : int = None) -> None: 33 """ 34 Sets the memory size in samples per channel. The memory size setting must be set before transferring 35 data to the card. (see register `SPC_MEMSIZE` in the manual) 36 37 Parameters 38 ---------- 39 segment_size : int | pint.Quantity 40 the size of a single segment in memory in Samples 41 """ 42 43 if segment_size is not None: 44 segment_size = UnitConversion.convert(segment_size, units.S, int) 45 self.card.set_i(SPC_SEGMENTSIZE, segment_size) 46 segment_size = self.card.get_i(SPC_SEGMENTSIZE) 47 self._segment_size = segment_size 48 49 def post_trigger(self, num_samples : int = None) -> int: 50 """ 51 Set the number of post trigger samples (see register `SPC_POSTTRIGGER` in the manual) 52 53 Parameters 54 ---------- 55 num_samples : int | pint.Quantity 56 the number of post trigger samples 57 58 Returns 59 ------- 60 int 61 the number of post trigger samples 62 """ 63 64 post_trigger = super().post_trigger(num_samples) 65 self._pre_trigger = self._segment_size - post_trigger 66 return post_trigger 67 68 def allocate_buffer(self, segment_samples : int, num_segments : int = None) -> None: 69 """ 70 Memory allocation for the buffer that is used for communicating with the card 71 72 Parameters 73 ---------- 74 segment_samples : int | pint.Quantity 75 use the number of samples and get the number of active channels and bytes per samples directly from the card 76 num_segments : int = None 77 the number of segments that are used for the multiple recording mode 78 """ 79 80 segment_samples = UnitConversion.convert(segment_samples, units.S, int) 81 num_segments = UnitConversion.convert(num_segments, units.S, int) 82 self.segment_samples(segment_samples) 83 if num_segments is None: 84 self._num_segments = self._memory_size // segment_samples 85 else: 86 self._num_segments = num_segments 87 88 super().allocate_buffer(segment_samples * self._num_segments, no_reshape=True) 89 90 num_channels = self.card.active_channels() 91 if self.bits_per_sample > 1 and not self._12bit_mode: 92 self.card._print(f"{self._num_segments} segments of {segment_samples} samples with {num_channels} channels") 93 self.buffer = self.buffer.reshape((self._num_segments, segment_samples, num_channels), order='C') # index definition: [segment, sample, channel] ! 94 95 def time_data(self, total_num_samples : int = None, return_units = units.s) -> npt.NDArray: 96 """ 97 Get the time array for the data buffer 98 99 Parameters 100 ---------- 101 total_num_samples : int | pint.Quantity 102 the total number of samples 103 return_units : pint.Unit 104 the units of the time array 105 106 Returns 107 ------- 108 numpy array 109 the time array 110 """ 111 112 if total_num_samples is None: 113 total_num_samples = self._buffer_samples // self._num_segments 114 return super().time_data(total_num_samples, return_units) 115 116 def unpack_12bit_buffer(self, data : npt.NDArray[np.int_] = None) -> npt.NDArray[np.int_]: 117 """ 118 Unpacks the 12-bit packed data to 16-bit data 119 120 Parameters 121 ---------- 122 data : numpy array 123 the packed data 124 125 Returns 126 ------- 127 numpy array 128 the unpacked 16bit buffer 129 """ 130 buffer_12bit = super().unpack_12bit_buffer(data) 131 return buffer_12bit.reshape((self._num_segments, self.num_channels, self._segment_size), order='C') 132 133 134 def __next__(self) -> npt.ArrayLike: 135 """ 136 This method is called when the next element is requested from the iterator 137 138 Returns 139 ------- 140 npt.ArrayLike 141 the next data block 142 143 Raises 144 ------ 145 StopIteration 146 """ 147 super().__next__() 148 user_pos = self.avail_user_pos() 149 current_segment = user_pos // self._segment_size 150 current_pos_in_segment = user_pos % self._segment_size 151 final_segment = ((user_pos+self._notify_samples) // self._segment_size) 152 final_pos_in_segment = (user_pos+self._notify_samples) % self._segment_size 153 154 self.card._print("NumSamples = {}, CurrentSegment = {}, CurrentPos = {}, FinalSegment = {}, FinalPos = {}".format(self._notify_samples, current_segment, current_pos_in_segment, final_segment, final_pos_in_segment)) 155 156 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._pre_trigger = None 29 self._segment_size = 0 30 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
32 def segment_samples(self, segment_size : int = None) -> None: 33 """ 34 Sets the memory size in samples per channel. The memory size setting must be set before transferring 35 data to the card. (see register `SPC_MEMSIZE` in the manual) 36 37 Parameters 38 ---------- 39 segment_size : int | pint.Quantity 40 the size of a single segment in memory in Samples 41 """ 42 43 if segment_size is not None: 44 segment_size = UnitConversion.convert(segment_size, units.S, int) 45 self.card.set_i(SPC_SEGMENTSIZE, segment_size) 46 segment_size = self.card.get_i(SPC_SEGMENTSIZE) 47 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
49 def post_trigger(self, num_samples : int = None) -> int: 50 """ 51 Set the number of post trigger samples (see register `SPC_POSTTRIGGER` in the manual) 52 53 Parameters 54 ---------- 55 num_samples : int | pint.Quantity 56 the number of post trigger samples 57 58 Returns 59 ------- 60 int 61 the number of post trigger samples 62 """ 63 64 post_trigger = super().post_trigger(num_samples) 65 self._pre_trigger = self._segment_size - post_trigger 66 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
68 def allocate_buffer(self, segment_samples : int, num_segments : int = None) -> None: 69 """ 70 Memory allocation for the buffer that is used for communicating with the card 71 72 Parameters 73 ---------- 74 segment_samples : int | pint.Quantity 75 use the number of samples and get the number of active channels and bytes per samples directly from the card 76 num_segments : int = None 77 the number of segments that are used for the multiple recording mode 78 """ 79 80 segment_samples = UnitConversion.convert(segment_samples, units.S, int) 81 num_segments = UnitConversion.convert(num_segments, units.S, int) 82 self.segment_samples(segment_samples) 83 if num_segments is None: 84 self._num_segments = self._memory_size // segment_samples 85 else: 86 self._num_segments = num_segments 87 88 super().allocate_buffer(segment_samples * self._num_segments, no_reshape=True) 89 90 num_channels = self.card.active_channels() 91 if self.bits_per_sample > 1 and not self._12bit_mode: 92 self.card._print(f"{self._num_segments} segments of {segment_samples} samples with {num_channels} channels") 93 self.buffer = self.buffer.reshape((self._num_segments, segment_samples, num_channels), order='C') # index definition: [segment, sample, channel] !
Memory allocation for the buffer that is used for communicating with the card
Parameters
- segment_samples (int | pint.Quantity): use the number of samples and get the number of active channels and bytes per samples directly from the card
- num_segments (int = None): the number of segments that are used for the multiple recording mode
95 def time_data(self, total_num_samples : int = None, return_units = units.s) -> npt.NDArray: 96 """ 97 Get the time array for the data buffer 98 99 Parameters 100 ---------- 101 total_num_samples : int | pint.Quantity 102 the total number of samples 103 return_units : pint.Unit 104 the units of the time array 105 106 Returns 107 ------- 108 numpy array 109 the time array 110 """ 111 112 if total_num_samples is None: 113 total_num_samples = self._buffer_samples // self._num_segments 114 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
116 def unpack_12bit_buffer(self, data : npt.NDArray[np.int_] = None) -> npt.NDArray[np.int_]: 117 """ 118 Unpacks the 12-bit packed data to 16-bit data 119 120 Parameters 121 ---------- 122 data : numpy array 123 the packed data 124 125 Returns 126 ------- 127 numpy array 128 the unpacked 16bit buffer 129 """ 130 buffer_12bit = super().unpack_12bit_buffer(data) 131 return buffer_12bit.reshape((self._num_segments, self.num_channels, self._segment_size), order='C')
Unpacks the 12-bit packed data to 16-bit data
Parameters
- data (numpy array): the packed data
Returns
- numpy array: the unpacked 16bit buffer
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) 52 self._alignment = self.card.get_i(SPC_GATE_LEN_ALIGNMENT) 53 54 def start_buffer_transfer(self, *args, **kwargs): 55 super().start_buffer_transfer(*args, **kwargs) 56 if self.direction is Direction.Acquisition: 57 self.timestamp.start_buffer_transfer(M2CMD_EXTRA_STARTDMA) 58 59 def post_trigger(self, num_samples : int = None) -> int: 60 """ 61 Set the number of post trigger samples (see register `SPC_POSTTRIGGER` in the manual) 62 63 Parameters 64 ---------- 65 num_samples : int | pint.Quantity 66 the number of post trigger samples 67 68 Returns 69 ------- 70 int 71 the number of post trigger samples 72 """ 73 74 if not self._fifo_mode and self._memory_size < num_samples: 75 raise ValueError("The number of post trigger samples needs to be smaller than the total number of samples") 76 if num_samples is not None: 77 num_samples = UnitConversion.convert(num_samples, units.Sa, int) 78 self.card.set_i(SPC_POSTTRIGGER, num_samples) 79 self._post_trigger = self.card.get_i(SPC_POSTTRIGGER) 80 return self._post_trigger 81 82 def alignment(self) -> int: 83 """ 84 Get the alignment of the end of the gated data buffer (see register `SPC_GATE_LEN_ALIGNMENT` in the manual) 85 86 Returns 87 ------- 88 int 89 the number of samples to align the end of the gated data buffer 90 """ 91 return self._alignment 92 93 def num_gates(self, num_gates : int = None) -> int: 94 """ 95 FIFO only: set the number of gates to be acquired (see register `SPC_LOOPS` in the manual) 96 97 Parameters 98 ---------- 99 num_gates : int 100 the number of gates to be acquired 101 102 Returns 103 ------- 104 int 105 the number of gates to be acquired 106 """ 107 108 if num_gates is not None: 109 self.card.set_i(SPC_LOOPS, num_gates) 110 self._num_gates = self.card.get_i(SPC_LOOPS) 111 return self._num_gates 112 113 def gate_counter(self) -> int: 114 """ 115 Get the number of gate events since acquisition start (see register 'SPC_TRIGGERCOUNTER' in chapter `Trigger` in the manual) 116 117 Returns 118 ------- 119 int 120 The gate counter 121 """ 122 123 return self.card.get_i(SPC_TRIGGERCOUNTER) 124 125 def available_gates(self) -> tuple[int, int]: 126 """ 127 Get the number of available gates in the timestamp buffer 128 129 Returns 130 ------- 131 tuple(int, int) 132 The current position and the number of available gates from the timestamp buffer 133 """ 134 135 return self.timestamp.avail_user_pos() // 2, self.timestamp.avail_user_len() // 2 136 137 def __iter__(self): 138 """ 139 Returns an iterator object and initializes the iterator index. 140 141 Returns 142 ------- 143 iterable 144 An iterator object for the class. 145 """ 146 iter = super().__iter__() 147 self.iterator_index = -1 148 return iter 149 150 iterator_index : int = -1 151 gate_count : int = 0 152 _start : int = 0 153 _end : int = 0 154 _aligned_end : int = 0 155 _current_num_samples : int = 0 156 def __next__(self): 157 if self.direction is not Direction.Acquisition: 158 raise ValueError("Iterating the Gated class can only be used with acquisition") 159 if self._fifo_mode and not self._polling: 160 raise SpcmException("Polling is required for fifo gated acquisition. Please set the polling mode to True") 161 162 self.iterator_index += 1 163 164 # notify the card that data is available or read, but only after the first block 165 if self.iterator_index > 0 and self._auto_avail_card_len: 166 self.flush() 167 168 # Check if all the gates have been acquired 169 if self._num_gates > 0 and self.iterator_index >= self._num_gates: self.stop_next() 170 ts_len = self.timestamp.avail_user_len() 171 if not self._fifo_mode and ts_len < 2: self.stop_next() 172 173 while self._polling: 174 ts_len = self.timestamp.avail_user_len() 175 self.card._print(f"Available time stamps: {ts_len}", end="\r") 176 if ts_len >= 2: 177 break 178 time.sleep(self._polling_timer) 179 180 # Get the start and end of the gate event 181 self._start = self.avail_user_pos() 182 length = self.timestamp.buffer[2*self.iterator_index+1, 0] - self.timestamp.buffer[2*self.iterator_index+0, 0] 183 self.card._print(f"Gate {self.iterator_index} - Start: {self._start} - Length: {length}", end="\n") 184 segment_length = length + self._pre_trigger + self._post_trigger 185 self._end = self._start + segment_length 186 187 # 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 188 alignment = self.alignment() 189 length_with_alignment = (length // alignment + 1) * alignment 190 self._current_num_samples = length_with_alignment + self._pre_trigger + self._post_trigger 191 self._aligned_end = self._start + self._current_num_samples 192 193 # Wait for enough data to be available in the buffer to get the next gate 194 while self._polling: 195 user_len = self.avail_user_len() 196 self.card._print(f"Available data: {user_len} - Required data: {self._current_num_samples}", end="\r") 197 if user_len >= self._current_num_samples: 198 break 199 time.sleep(self._polling_timer) 200 201 self._current_samples += self._current_num_samples 202 if self._to_transfer_samples > 0 and self._to_transfer_samples <= self._current_samples: 203 self.stop_next() 204 205 # Return the view of the data buffer that contains only the data of the current gate 206 return self.buffer[:, self._start:self._end] 207 208 def stop_next(self): 209 """ 210 Stop the iteration and flush all the iterator parameters 211 """ 212 self.iterator_index = -1 213 self._start = 0 214 self._end = 0 215 self._aligned_end = 0 216 self._current_samples = 0 217 self._current_num_samples = 0 218 raise StopIteration 219 220 def flush(self): 221 """ 222 This method is used to tell the card that a notify size of data is freed up after reading (acquisition) or written to (generation) 223 """ 224 self.avail_card_len(self._current_num_samples) 225 self.timestamp.avail_card_len(2) # two time stamps per gate 226 227 def current_time_range(self, return_unit = None) -> int: 228 """ 229 Get the current time range of the data buffer 230 231 Parameters 232 ---------- 233 return_unit : pint.Unit 234 the unit to return the time range in 235 236 Returns 237 ------- 238 int or pint.Quantity 239 the current time range of the data buffer 240 """ 241 242 current_length = self._end - self._start 243 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) 244 time_range = UnitConversion.to_unit(time_range[:current_length] / self._sample_rate(), return_unit) 245 return time_range 246 247 def current_timestamps(self, return_unit = None) -> tuple: 248 """ 249 Get the current timestamps of the data buffer 250 251 Parameters 252 ---------- 253 return_unit : pint.Unit 254 the unit to return the timestamps in 255 256 Returns 257 ------- 258 tuple 259 the current timestamps of the data buffer 260 """ 261 262 ts_start = self.timestamp.buffer[2*self.iterator_index+0, 0] 263 ts_end = self.timestamp.buffer[2*self.iterator_index+1, 0] 264 ts_start = UnitConversion.to_unit(ts_start / self._sample_rate(), return_unit) 265 ts_end = UnitConversion.to_unit(ts_end / self._sample_rate(), return_unit) 266 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) 52 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
54 def start_buffer_transfer(self, *args, **kwargs): 55 super().start_buffer_transfer(*args, **kwargs) 56 if self.direction is Direction.Acquisition: 57 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
59 def post_trigger(self, num_samples : int = None) -> int: 60 """ 61 Set the number of post trigger samples (see register `SPC_POSTTRIGGER` in the manual) 62 63 Parameters 64 ---------- 65 num_samples : int | pint.Quantity 66 the number of post trigger samples 67 68 Returns 69 ------- 70 int 71 the number of post trigger samples 72 """ 73 74 if not self._fifo_mode and self._memory_size < num_samples: 75 raise ValueError("The number of post trigger samples needs to be smaller than the total number of samples") 76 if num_samples is not None: 77 num_samples = UnitConversion.convert(num_samples, units.Sa, int) 78 self.card.set_i(SPC_POSTTRIGGER, num_samples) 79 self._post_trigger = self.card.get_i(SPC_POSTTRIGGER) 80 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
82 def alignment(self) -> int: 83 """ 84 Get the alignment of the end of the gated data buffer (see register `SPC_GATE_LEN_ALIGNMENT` in the manual) 85 86 Returns 87 ------- 88 int 89 the number of samples to align the end of the gated data buffer 90 """ 91 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
93 def num_gates(self, num_gates : int = None) -> int: 94 """ 95 FIFO only: set the number of gates to be acquired (see register `SPC_LOOPS` in the manual) 96 97 Parameters 98 ---------- 99 num_gates : int 100 the number of gates to be acquired 101 102 Returns 103 ------- 104 int 105 the number of gates to be acquired 106 """ 107 108 if num_gates is not None: 109 self.card.set_i(SPC_LOOPS, num_gates) 110 self._num_gates = self.card.get_i(SPC_LOOPS) 111 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
113 def gate_counter(self) -> int: 114 """ 115 Get the number of gate events since acquisition start (see register 'SPC_TRIGGERCOUNTER' in chapter `Trigger` in the manual) 116 117 Returns 118 ------- 119 int 120 The gate counter 121 """ 122 123 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
125 def available_gates(self) -> tuple[int, int]: 126 """ 127 Get the number of available gates in the timestamp buffer 128 129 Returns 130 ------- 131 tuple(int, int) 132 The current position and the number of available gates from the timestamp buffer 133 """ 134 135 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
208 def stop_next(self): 209 """ 210 Stop the iteration and flush all the iterator parameters 211 """ 212 self.iterator_index = -1 213 self._start = 0 214 self._end = 0 215 self._aligned_end = 0 216 self._current_samples = 0 217 self._current_num_samples = 0 218 raise StopIteration
Stop the iteration and flush all the iterator parameters
220 def flush(self): 221 """ 222 This method is used to tell the card that a notify size of data is freed up after reading (acquisition) or written to (generation) 223 """ 224 self.avail_card_len(self._current_num_samples) 225 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)
227 def current_time_range(self, return_unit = None) -> int: 228 """ 229 Get the current time range of the data buffer 230 231 Parameters 232 ---------- 233 return_unit : pint.Unit 234 the unit to return the time range in 235 236 Returns 237 ------- 238 int or pint.Quantity 239 the current time range of the data buffer 240 """ 241 242 current_length = self._end - self._start 243 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) 244 time_range = UnitConversion.to_unit(time_range[:current_length] / self._sample_rate(), return_unit) 245 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
247 def current_timestamps(self, return_unit = None) -> tuple: 248 """ 249 Get the current timestamps of the data buffer 250 251 Parameters 252 ---------- 253 return_unit : pint.Unit 254 the unit to return the timestamps in 255 256 Returns 257 ------- 258 tuple 259 the current timestamps of the data buffer 260 """ 261 262 ts_start = self.timestamp.buffer[2*self.iterator_index+0, 0] 263 ts_end = self.timestamp.buffer[2*self.iterator_index+1, 0] 264 ts_start = UnitConversion.to_unit(ts_start / self._sample_rate(), return_unit) 265 ts_end = UnitConversion.to_unit(ts_end / self._sample_rate(), return_unit) 266 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 def allocate_buffer(self, num_timestamps : int) -> None: 95 """ 96 Allocate the buffer for the timestamp data transfer 97 98 Parameters 99 ---------- 100 num_timestamps : int 101 The number of timestamps to be allocated 102 """ 103 104 num_timestamps = UnitConversion.convert(num_timestamps, units.S, int) 105 self.buffer_size = num_timestamps * self.bytes_per_ts 106 107 dwMask = self._buffer_alignment - 1 108 109 sample_type = np.int64 110 item_size = sample_type(0).itemsize 111 # allocate a buffer (numpy array) for DMA transfer: a little bigger one to have room for address alignment 112 databuffer_unaligned = np.zeros(((self._buffer_alignment + self.buffer_size) // item_size, ), dtype = sample_type) # half byte count at int16 sample (// = integer division) 113 # two numpy-arrays may share the same memory: skip the begin up to the alignment boundary (ArrayVariable[SKIP_VALUE:]) 114 # Address of data-memory from numpy-array: ArrayVariable.__array_interface__['data'][0] 115 start_pos_samples = ((self._buffer_alignment - (databuffer_unaligned.__array_interface__['data'][0] & dwMask)) // item_size) 116 self.buffer = databuffer_unaligned[start_pos_samples:start_pos_samples + (self.buffer_size // item_size)] # byte address but int16 sample: therefore / 2 117 self.buffer = self.buffer.reshape((num_timestamps, 2), order='C') # array items per timestamp, because the maximum item size is 8 bytes = 64 bits 118 119 def start_buffer_transfer(self, *args, direction=SPCM_DIR_CARDTOPC, notify_timestamps=0, transfer_offset=0, transfer_length=None) -> None: 120 """ 121 Start the transfer of the timestamp data to the card 122 123 Parameters 124 ---------- 125 *args : list 126 list of additonal arguments that are added as flags to the start dma command 127 """ 128 129 notify_size = 0 130 if notify_timestamps: 131 self._notify_timestamps = notify_timestamps 132 if self._notify_timestamps: 133 notify_size = self._notify_timestamps * self.bytes_per_ts 134 135 if transfer_offset: 136 transfer_offset_bytes = transfer_offset * self.bytes_per_ts 137 else: 138 transfer_offset_bytes = 0 139 140 if transfer_length is not None: 141 transfer_length_bytes = transfer_length * self.bytes_per_ts 142 else: 143 transfer_length_bytes = self.buffer_size 144 145 146 # we define the buffer for transfer and start the DMA transfer 147 self.card._print("Starting the Timestamp transfer and waiting until data is in board memory") 148 self._c_buffer = self.buffer.ctypes.data_as(c_void_p) 149 spcm_dwDefTransfer_i64(self.card._handle, self.buffer_type, direction, notify_size, self._c_buffer, transfer_offset_bytes, transfer_length_bytes) 150 cmd = 0 151 for arg in args: 152 cmd |= arg 153 self.card.cmd(cmd) 154 self.card._print("... timestamp data transfer started") 155 156 def avail_card_len(self, num_timestamps : int) -> None: 157 """ 158 Set the amount of timestamps that is available for reading of the timestamp buffer (see register 'SPC_TS_AVAIL_CARD_LEN' in chapter `Timestamp` in the manual) 159 160 Parameters 161 ---------- 162 num_timestamps : int 163 the amount of timestamps that is available for reading 164 """ 165 card_len = num_timestamps * self.bytes_per_ts 166 self.card.set_i(SPC_TS_AVAIL_CARD_LEN, card_len) 167 168 def avail_user_pos(self) -> int: 169 """ 170 Get the current position of the pointer in the timestamp buffer (see register 'SPC_TS_AVAIL_USER_POS' in chapter `Timestamp` in the manual) 171 172 Returns 173 ------- 174 int 175 pointer position in timestamps 176 """ 177 return self.card.get_i(SPC_TS_AVAIL_USER_POS) // self.bytes_per_ts 178 179 def avail_user_len(self) -> int: 180 """ 181 Get the current length of the data in timestamps in the timestamp buffer (see register 'SPC_TS_AVAIL_USER_LEN' in chapter `Timestamp` in the manual) 182 183 Returns 184 ------- 185 int 186 data length available in number of timestamps 187 """ 188 return self.card.get_i(SPC_TS_AVAIL_USER_LEN) // self.bytes_per_ts 189 190 191 # Iterator methods 192 _max_polling = 64 193 194 def to_transfer_timestamps(self, timestamps: int) -> None: 195 """ 196 This method sets the number of timestamps to transfer 197 198 Parameters 199 ---------- 200 timestamps : int 201 the number of timestamps to transfer 202 """ 203 self._to_transfer_timestamps = timestamps 204 205 def poll(self, polling_length : int = 0) -> npt.ArrayLike: 206 """ 207 This method is called when polling for timestamps 208 209 Parameters 210 ---------- 211 polling_length : int = 0 212 the number of timestamps to poll and wait for 213 214 Returns 215 ------- 216 npt.ArrayLike 217 the next data block 218 """ 219 220 while True: 221 user_pos = self.avail_user_pos() 222 user_len = self.avail_user_len() 223 if not polling_length and user_len >= 1: 224 self.avail_card_len(user_len) 225 return self.buffer[user_pos:user_pos+user_len, :] 226 elif user_len >= polling_length: 227 self.avail_card_len(polling_length) 228 return self.buffer[user_pos:user_pos+polling_length, :]
a class to control Spectrum Instrumentation cards with the timestamp functionality
For more information about what setups are available, please have a look at the user manual for your specific card
Parameters
ts_mode (int):
transfer_mode (int):
bits_per_ts (int = 0):
bytes_per_ts (int = 16):
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
94 def allocate_buffer(self, num_timestamps : int) -> None: 95 """ 96 Allocate the buffer for the timestamp data transfer 97 98 Parameters 99 ---------- 100 num_timestamps : int 101 The number of timestamps to be allocated 102 """ 103 104 num_timestamps = UnitConversion.convert(num_timestamps, units.S, int) 105 self.buffer_size = num_timestamps * self.bytes_per_ts 106 107 dwMask = self._buffer_alignment - 1 108 109 sample_type = np.int64 110 item_size = sample_type(0).itemsize 111 # allocate a buffer (numpy array) for DMA transfer: a little bigger one to have room for address alignment 112 databuffer_unaligned = np.zeros(((self._buffer_alignment + self.buffer_size) // item_size, ), dtype = sample_type) # half byte count at int16 sample (// = integer division) 113 # two numpy-arrays may share the same memory: skip the begin up to the alignment boundary (ArrayVariable[SKIP_VALUE:]) 114 # Address of data-memory from numpy-array: ArrayVariable.__array_interface__['data'][0] 115 start_pos_samples = ((self._buffer_alignment - (databuffer_unaligned.__array_interface__['data'][0] & dwMask)) // item_size) 116 self.buffer = databuffer_unaligned[start_pos_samples:start_pos_samples + (self.buffer_size // item_size)] # byte address but int16 sample: therefore / 2 117 self.buffer = self.buffer.reshape((num_timestamps, 2), order='C') # array items per timestamp, because the maximum item size is 8 bytes = 64 bits
Allocate the buffer for the timestamp data transfer
Parameters
- num_timestamps (int): The number of timestamps to be allocated
119 def start_buffer_transfer(self, *args, direction=SPCM_DIR_CARDTOPC, notify_timestamps=0, transfer_offset=0, transfer_length=None) -> None: 120 """ 121 Start the transfer of the timestamp data to the card 122 123 Parameters 124 ---------- 125 *args : list 126 list of additonal arguments that are added as flags to the start dma command 127 """ 128 129 notify_size = 0 130 if notify_timestamps: 131 self._notify_timestamps = notify_timestamps 132 if self._notify_timestamps: 133 notify_size = self._notify_timestamps * self.bytes_per_ts 134 135 if transfer_offset: 136 transfer_offset_bytes = transfer_offset * self.bytes_per_ts 137 else: 138 transfer_offset_bytes = 0 139 140 if transfer_length is not None: 141 transfer_length_bytes = transfer_length * self.bytes_per_ts 142 else: 143 transfer_length_bytes = self.buffer_size 144 145 146 # we define the buffer for transfer and start the DMA transfer 147 self.card._print("Starting the Timestamp transfer and waiting until data is in board memory") 148 self._c_buffer = self.buffer.ctypes.data_as(c_void_p) 149 spcm_dwDefTransfer_i64(self.card._handle, self.buffer_type, direction, notify_size, self._c_buffer, transfer_offset_bytes, transfer_length_bytes) 150 cmd = 0 151 for arg in args: 152 cmd |= arg 153 self.card.cmd(cmd) 154 self.card._print("... timestamp data transfer started")
Start the transfer of the timestamp data to the card
Parameters
- *args (list): list of additonal arguments that are added as flags to the start dma command
156 def avail_card_len(self, num_timestamps : int) -> None: 157 """ 158 Set the amount of timestamps that is available for reading of the timestamp buffer (see register 'SPC_TS_AVAIL_CARD_LEN' in chapter `Timestamp` in the manual) 159 160 Parameters 161 ---------- 162 num_timestamps : int 163 the amount of timestamps that is available for reading 164 """ 165 card_len = num_timestamps * self.bytes_per_ts 166 self.card.set_i(SPC_TS_AVAIL_CARD_LEN, card_len)
Set the amount of timestamps that is available for reading of the timestamp buffer (see register 'SPC_TS_AVAIL_CARD_LEN' in chapter Timestamp
in the manual)
Parameters
- num_timestamps (int): the amount of timestamps that is available for reading
168 def avail_user_pos(self) -> int: 169 """ 170 Get the current position of the pointer in the timestamp buffer (see register 'SPC_TS_AVAIL_USER_POS' in chapter `Timestamp` in the manual) 171 172 Returns 173 ------- 174 int 175 pointer position in timestamps 176 """ 177 return self.card.get_i(SPC_TS_AVAIL_USER_POS) // self.bytes_per_ts
Get the current position of the pointer in the timestamp buffer (see register 'SPC_TS_AVAIL_USER_POS' in chapter Timestamp
in the manual)
Returns
- int: pointer position in timestamps
179 def avail_user_len(self) -> int: 180 """ 181 Get the current length of the data in timestamps in the timestamp buffer (see register 'SPC_TS_AVAIL_USER_LEN' in chapter `Timestamp` in the manual) 182 183 Returns 184 ------- 185 int 186 data length available in number of timestamps 187 """ 188 return self.card.get_i(SPC_TS_AVAIL_USER_LEN) // self.bytes_per_ts
Get the current length of the data in timestamps in the timestamp buffer (see register 'SPC_TS_AVAIL_USER_LEN' in chapter Timestamp
in the manual)
Returns
- int: data length available in number of timestamps
194 def to_transfer_timestamps(self, timestamps: int) -> None: 195 """ 196 This method sets the number of timestamps to transfer 197 198 Parameters 199 ---------- 200 timestamps : int 201 the number of timestamps to transfer 202 """ 203 self._to_transfer_timestamps = timestamps
This method sets the number of timestamps to transfer
Parameters
- timestamps (int): the number of timestamps to transfer
205 def poll(self, polling_length : int = 0) -> npt.ArrayLike: 206 """ 207 This method is called when polling for timestamps 208 209 Parameters 210 ---------- 211 polling_length : int = 0 212 the number of timestamps to poll and wait for 213 214 Returns 215 ------- 216 npt.ArrayLike 217 the next data block 218 """ 219 220 while True: 221 user_pos = self.avail_user_pos() 222 user_len = self.avail_user_len() 223 if not polling_length and user_len >= 1: 224 self.avail_card_len(user_len) 225 return self.buffer[user_pos:user_pos+user_len, :] 226 elif user_len >= polling_length: 227 self.avail_card_len(polling_length) 228 return self.buffer[user_pos:user_pos+polling_length, :]
This method is called when polling for timestamps
Parameters
- polling_length (int = 0): the number of timestamps to poll and wait for
Returns
- npt.ArrayLike: the next data block
11class Sequence(DataTransfer): 12 """ 13 a high-level class to control the sequence mode 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 def __init__(self, card, *args, **kwargs) -> None: 21 super().__init__(card, *args, **kwargs) 22 23 def max_segments(self, max_segments : int = 0) -> int: 24 """ 25 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) 26 27 Parameters 28 ---------- 29 max_segments : int 30 The maximum number of segments that can be used in the sequence mode 31 32 Returns 33 ------- 34 max_segments : int 35 The actual maximum number of segments that can be used in the sequence mode 36 """ 37 if max_segments: 38 self.card.set_i(SPC_SEQMODE_MAXSEGMENTS, max_segments) 39 return self.card.get_i(SPC_SEQMODE_MAXSEGMENTS) 40 41 def write_segment(self, segment : int = None) -> int: 42 """ 43 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) 44 45 Parameters 46 ---------- 47 segment : int 48 The segment to be addresses 49 50 Returns 51 ------- 52 segment : int 53 The segment to be addresses 54 """ 55 56 if segment is not None: 57 self.card.set_i(SPC_SEQMODE_WRITESEGMENT, segment) 58 return self.card.get_i(SPC_SEQMODE_WRITESEGMENT) 59 60 def segment_size(self, segment_size : int = None, return_unit = None) -> int: 61 """ 62 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) 63 64 Parameters 65 ---------- 66 segment_size : int | pint.Quantity 67 The size of the segment in samples 68 69 Returns 70 ------- 71 segment_size : int 72 The size of the segment in samples 73 """ 74 75 if segment_size is not None: 76 segment_size = UnitConversion.convert(segment_size, units.Sa, int) 77 self.card.set_i(SPC_SEQMODE_SEGMENTSIZE, segment_size) 78 return_value = self.card.get_i(SPC_SEQMODE_SEGMENTSIZE) 79 if return_unit is not None: return UnitConversion.to_unit(return_value, return_unit) 80 return return_value 81 82 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]: 83 """ 84 Defines the step memory for the current selected memory segment. (see register 'SPC_SEQMODE_STEPMEM0' in chapter `Sequence Mode` in the manual) 85 86 Parameters 87 ---------- 88 step_index : int 89 The index of the current step 90 next_step_index : int 91 The index of the next step in the sequence 92 segment_index : int 93 The index of the segment associated to the step 94 loops : int 95 The number of times the segment is looped 96 flags : int 97 The flags for the step 98 99 Returns 100 ------- 101 next_step_index : int 102 The index of the next step in the sequence 103 segment_index : int 104 The index of the segment associated to the step 105 loops : int 106 The number of times the segment is looped 107 flags : int 108 The flags for the step 109 110 """ 111 qwSequenceEntry = 0 112 113 # setup register value 114 if next_step_index is not None and segment_index is not None and loops is not None and flags is not None: 115 qwSequenceEntry = (flags & ~SPCSEQ_LOOPMASK) | (loops & SPCSEQ_LOOPMASK) 116 qwSequenceEntry <<= 32 117 qwSequenceEntry |= ((next_step_index << 16) & SPCSEQ_NEXTSTEPMASK) | (int(segment_index) & SPCSEQ_SEGMENTMASK) 118 self.card.set_i(SPC_SEQMODE_STEPMEM0 + step_index, qwSequenceEntry) 119 120 qwSequenceEntry = self.card.get_i(SPC_SEQMODE_STEPMEM0 + step_index) 121 return (qwSequenceEntry & SPCSEQ_NEXTSTEPMASK) >> 16, qwSequenceEntry & SPCSEQ_SEGMENTMASK, (qwSequenceEntry >> 32) & SPCSEQ_LOOPMASK, (qwSequenceEntry >> 32) & ~SPCSEQ_LOOPMASK 122 123 124 def start_step(self, start_step_index : int = None) -> int: 125 """ 126 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) 127 128 Parameters 129 ---------- 130 start_step_index : int 131 The index of the start step 132 133 Returns 134 ------- 135 start_step_index : int 136 The index of the start step 137 """ 138 139 if start_step_index is not None: 140 self.card.set_i(SPC_SEQMODE_STARTSTEP, start_step_index) 141 return self.card.get_i(SPC_SEQMODE_STARTSTEP) 142 143 def status(self) -> int: 144 """ 145 Reads the status of the sequence mode. (see register 'SPC_SEQMODE_STATUS' in chapter `Sequence Mode` in the manual) 146 147 Returns 148 ------- 149 status : int 150 The status of the sequence mode 151 152 """ 153 return self.card.get_i(SPC_SEQMODE_STATUS)
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.
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
23 def max_segments(self, max_segments : int = 0) -> int: 24 """ 25 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) 26 27 Parameters 28 ---------- 29 max_segments : int 30 The maximum number of segments that can be used in the sequence mode 31 32 Returns 33 ------- 34 max_segments : int 35 The actual maximum number of segments that can be used in the sequence mode 36 """ 37 if max_segments: 38 self.card.set_i(SPC_SEQMODE_MAXSEGMENTS, max_segments) 39 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
41 def write_segment(self, segment : int = None) -> int: 42 """ 43 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) 44 45 Parameters 46 ---------- 47 segment : int 48 The segment to be addresses 49 50 Returns 51 ------- 52 segment : int 53 The segment to be addresses 54 """ 55 56 if segment is not None: 57 self.card.set_i(SPC_SEQMODE_WRITESEGMENT, segment) 58 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): The segment to be addresses
Returns
- segment (int): The segment to be addresses
60 def segment_size(self, segment_size : int = None, return_unit = None) -> int: 61 """ 62 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) 63 64 Parameters 65 ---------- 66 segment_size : int | pint.Quantity 67 The size of the segment in samples 68 69 Returns 70 ------- 71 segment_size : int 72 The size of the segment in samples 73 """ 74 75 if segment_size is not None: 76 segment_size = UnitConversion.convert(segment_size, units.Sa, int) 77 self.card.set_i(SPC_SEQMODE_SEGMENTSIZE, segment_size) 78 return_value = self.card.get_i(SPC_SEQMODE_SEGMENTSIZE) 79 if return_unit is not None: return UnitConversion.to_unit(return_value, return_unit) 80 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
82 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]: 83 """ 84 Defines the step memory for the current selected memory segment. (see register 'SPC_SEQMODE_STEPMEM0' in chapter `Sequence Mode` in the manual) 85 86 Parameters 87 ---------- 88 step_index : int 89 The index of the current step 90 next_step_index : int 91 The index of the next step in the sequence 92 segment_index : int 93 The index of the segment associated to the step 94 loops : int 95 The number of times the segment is looped 96 flags : int 97 The flags for the step 98 99 Returns 100 ------- 101 next_step_index : int 102 The index of the next step in the sequence 103 segment_index : int 104 The index of the segment associated to the step 105 loops : int 106 The number of times the segment is looped 107 flags : int 108 The flags for the step 109 110 """ 111 qwSequenceEntry = 0 112 113 # setup register value 114 if next_step_index is not None and segment_index is not None and loops is not None and flags is not None: 115 qwSequenceEntry = (flags & ~SPCSEQ_LOOPMASK) | (loops & SPCSEQ_LOOPMASK) 116 qwSequenceEntry <<= 32 117 qwSequenceEntry |= ((next_step_index << 16) & SPCSEQ_NEXTSTEPMASK) | (int(segment_index) & SPCSEQ_SEGMENTMASK) 118 self.card.set_i(SPC_SEQMODE_STEPMEM0 + step_index, qwSequenceEntry) 119 120 qwSequenceEntry = self.card.get_i(SPC_SEQMODE_STEPMEM0 + step_index) 121 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
124 def start_step(self, start_step_index : int = None) -> int: 125 """ 126 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) 127 128 Parameters 129 ---------- 130 start_step_index : int 131 The index of the start step 132 133 Returns 134 ------- 135 start_step_index : int 136 The index of the start step 137 """ 138 139 if start_step_index is not None: 140 self.card.set_i(SPC_SEQMODE_STARTSTEP, start_step_index) 141 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
143 def status(self) -> int: 144 """ 145 Reads the status of the sequence mode. (see register 'SPC_SEQMODE_STATUS' in chapter `Sequence Mode` in the manual) 146 147 Returns 148 ------- 149 status : int 150 The status of the sequence mode 151 152 """ 153 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
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 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 if self._bytes_per_sample == 2: 73 return np.int16 74 return np.int32
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
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 if self._bytes_per_sample == 2: 73 return np.int16 74 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
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
100class SpcmException(Exception): 101 """a container class for handling driver level errors 102 103 Examples 104 ---------- 105 ```python 106 raise SpcmException(handle=card.handle()) 107 raise SpcmException(register=0, value=0, text="Some weird error") 108 ``` 109 110 Parameters 111 --------- 112 error : SpcmError 113 the error that induced the raising of the exception 114 115 """ 116 error = None 117 118 def __init__(self, error = None, register = None, value = None, text = None) -> None: 119 """ 120 Constructs exception object and an associated error object, either by getting 121 the last error from the card specified by the handle or using the information 122 coming from the parameters register, value and text 123 124 Parameters 125 ---------- 126 handle : drv_handle (optional) 127 a card handle to obtain the last error 128 register, value and text : int, int, str (optional) 129 parameters to define an error that is not raised by a driver error 130 """ 131 if error: self.error = error 132 if register or value or text: 133 self.error = SpcmError(register=register, value=value, text=text) 134 135 136 def __str__(self) -> str: 137 """ 138 Returns a human-readable text of the last error connected to the exception 139 140 Class Parameters 141 ---------- 142 self.error 143 144 Returns 145 ------- 146 str 147 the human-readable text as return by the error 148 """ 149 150 return str(self.error)
a container class for handling driver level errors
Examples
raise SpcmException(handle=card.handle())
raise SpcmException(register=0, value=0, text="Some weird error")
Parameters
- error (SpcmError): the error that induced the raising of the exception
118 def __init__(self, error = None, register = None, value = None, text = None) -> None: 119 """ 120 Constructs exception object and an associated error object, either by getting 121 the last error from the card specified by the handle or using the information 122 coming from the parameters register, value and text 123 124 Parameters 125 ---------- 126 handle : drv_handle (optional) 127 a card handle to obtain the last error 128 register, value and text : int, int, str (optional) 129 parameters to define an error that is not raised by a driver error 130 """ 131 if error: self.error = error 132 if register or value or text: 133 self.error = SpcmError(register=register, value=value, text=text)
Constructs exception object and an associated error object, either by getting the last error from the card specified by the handle or using the information coming from the parameters register, value and text
Parameters
- handle (drv_handle (optional)): a card handle to obtain the last error
- register, value and text (int, int, str (optional)): parameters to define an error that is not raised by a driver error
152class SpcmTimeout(Exception): 153 """a container class for handling specific timeout exceptions""" 154 pass
a container class for handling specific timeout exceptions
156class SpcmDeviceNotFound(SpcmException): 157 """a container class for handling specific device not found exceptions""" 158 pass
a container class for handling specific device not found exceptions
10class SpcmError(): 11 """a container class for handling driver level errors 12 13 Examples 14 ---------- 15 ```python 16 error = SpcmError(handle=card.handle()) 17 error = SpcmError(register=0, value=0, text="Some weird error") 18 ``` 19 20 Parameters 21 --------- 22 register : int 23 the register address that triggered the error 24 25 value : int 26 the value that was written to the register address 27 28 text : str 29 the human-readable text associated with the error 30 31 """ 32 33 register : int = 0 34 value : int = 0 35 text : str = "" 36 _handle = None 37 38 def __init__(self, handle = None, register = None, value = None, text = None) -> None: 39 """ 40 Constructs an error object, either by getting the last error from the card specified by the handle 41 or using the information coming from the parameters register, value and text 42 43 Parameters 44 ---------- 45 handle : pyspcm.drv_handle (optional) 46 a card handle to obtain the last error 47 register, value and text : int, int, str (optional) 48 parameters to define an error that is not raised by a driver error 49 """ 50 if handle: 51 self._handle = handle 52 self.get_info() 53 if register: self.register = register 54 if value: self.value = value 55 if text: self.text = text 56 57 def get_info(self) -> int: 58 """ 59 Gets the last error registered by the card and puts it in the object 60 61 Class Parameters 62 ---------- 63 self.register 64 self.value 65 self.text 66 67 Returns 68 ------- 69 int 70 Error number of the spcm_dwGetErrorInfo_i32 class 71 """ 72 73 register = uint32(0) 74 value = int32(0) 75 text = create_string_buffer(ERRORTEXTLEN) 76 dwErr = spcm_dwGetErrorInfo_i32(self._handle, byref(register), byref(value), byref(text)) 77 self.register = register.value 78 self.value = value.value 79 self.text = text.value.decode('utf-8') 80 return dwErr 81 82 def __str__(self) -> str: 83 """ 84 Returns a human-readable text of the last error 85 86 Class Parameters 87 ---------- 88 self.register 89 self.value 90 self.text 91 92 Returns 93 ------- 94 str 95 the human-readable text as saved in self.text. 96 """ 97 98 return str(self.text)
a container class for handling driver level errors
Examples
error = SpcmError(handle=card.handle())
error = SpcmError(register=0, value=0, text="Some weird error")
Parameters
- register (int): the register address that triggered the error
- value (int): the value that was written to the register address
- text (str): the human-readable text associated with the error
38 def __init__(self, handle = None, register = None, value = None, text = None) -> None: 39 """ 40 Constructs an error object, either by getting the last error from the card specified by the handle 41 or using the information coming from the parameters register, value and text 42 43 Parameters 44 ---------- 45 handle : pyspcm.drv_handle (optional) 46 a card handle to obtain the last error 47 register, value and text : int, int, str (optional) 48 parameters to define an error that is not raised by a driver error 49 """ 50 if handle: 51 self._handle = handle 52 self.get_info() 53 if register: self.register = register 54 if value: self.value = value 55 if text: self.text = text
Constructs an error object, either by getting the last error from the card specified by the handle or using the information coming from the parameters register, value and text
Parameters
- handle (pyspcm.drv_handle (optional)): a card handle to obtain the last error
- register, value and text (int, int, str (optional)): parameters to define an error that is not raised by a driver error
57 def get_info(self) -> int: 58 """ 59 Gets the last error registered by the card and puts it in the object 60 61 Class Parameters 62 ---------- 63 self.register 64 self.value 65 self.text 66 67 Returns 68 ------- 69 int 70 Error number of the spcm_dwGetErrorInfo_i32 class 71 """ 72 73 register = uint32(0) 74 value = int32(0) 75 text = create_string_buffer(ERRORTEXTLEN) 76 dwErr = spcm_dwGetErrorInfo_i32(self._handle, byref(register), byref(value), byref(text)) 77 self.register = register.value 78 self.value = value.value 79 self.text = text.value.decode('utf-8') 80 return dwErr
Gets the last error registered by the card and puts it in the object
Class Parameters
self.register self.value self.text
Returns
- int: Error number of the spcm_dwGetErrorInfo_i32 class
52class SCAPPTransfer(DataTransfer): 53 """ 54 Class for data transfer between the card and the host using the SCAPP API. 55 """ 56 57 direction : Direction = None 58 59 def __init__(self, card : Card, direction : Direction = Direction.Acquisition): 60 if not _cuda_support: 61 raise ImportError("CUDA support is not available. Please install the cupy and cuda-python packages.") 62 super().__init__(card) 63 scapp_feature = bool(self.card._features & SPCM_FEAT_SCAPP) 64 if not scapp_feature: 65 raise SpcmException(text="The card does not have the SCAPP option installed. SCAPP is a add-on feature that needs to be bought separately, please contact info@spec.de and ask for the SCAPP option for the card with serial number {}".format(self.card.sn())) 66 self.direction = direction 67 self.iterator_index = 0 68 69 def allocate_buffer(self, num_samples : int) -> None: 70 """ 71 Memory allocation for the buffer that is used for communicating with the card 72 73 Parameters 74 ---------- 75 num_samples : int | pint.Quantity = None 76 use the number of samples an get the number of active channels and bytes per samples directly from the card 77 """ 78 79 self.buffer_samples = UnitConversion.convert(num_samples, units.Sa, int) 80 # Allocate RDMA buffer 81 self.buffer = cp.empty((self.num_channels, self.buffer_samples), dtype = self.numpy_type(), order='F') 82 flag = 1 83 checkCudaErrors(cuda.cuPointerSetAttribute(flag, cuda.CUpointer_attribute.CU_POINTER_ATTRIBUTE_SYNC_MEMOPS, self.buffer.data.ptr)) 84 85 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: 86 """ 87 Setup an RDMA transfer. 88 89 Parameters 90 ---------- 91 *args : list 92 Additional commands that are send to the card. 93 direction : int 94 the direction of the transfer 95 notify_samples : int 96 Size of the part of the buffer that is used for notifications. 97 transfer_offset : int 98 the offset of the transfer 99 transfer_length : int 100 Total length of the transfer buffer. 101 """ 102 103 # only change this locally 104 if notify_samples is not None: 105 notify_size = notify_samples * self.num_channels * self.bytes_per_sample 106 else: 107 notify_size = self.notify_size 108 transfer_offset = UnitConversion.convert(transfer_offset, units.Sa, int) 109 transfer_length = UnitConversion.convert(transfer_length, units.Sa, int) 110 111 if self.buffer is None: 112 raise SpcmException(text="No buffer defined for transfer") 113 if buffer_type: 114 self.buffer_type = buffer_type 115 if direction is None: 116 if self.direction == Direction.Acquisition: 117 direction = SPCM_DIR_CARDTOGPU 118 elif self.direction == Direction.Generation: 119 direction = SPCM_DIR_GPUTOCARD 120 else: 121 raise SpcmException(text="Please define a direction for transfer (SPCM_DIR_CARDTOGPU or SPCM_DIR_GPUTOCARD)") 122 123 if self._notify_samples != 0 and np.remainder(self.buffer_samples, self._notify_samples) and exception_num_samples: 124 raise SpcmException("The number of samples needs to be a multiple of the notify samples.") 125 126 if transfer_offset: 127 transfer_offset_bytes = self.samples_to_bytes(transfer_offset) 128 else: 129 transfer_offset_bytes = 0 130 131 self.buffer_samples = transfer_length 132 133 # we define the buffer for transfer 134 self.card._print("Starting the DMA transfer and waiting until data is in board memory") 135 self.card._check_error(spcm_dwDefTransfer_i64(self.card._handle, self.buffer_type, direction, notify_size, c_void_p(self.buffer.data.ptr), transfer_offset_bytes, self.buffer_size)) 136 137 # Execute additional commands if available 138 if args: 139 cmd = 0 140 for arg in args: 141 cmd |= arg 142 self.card.cmd(cmd) 143 self.card._print("... SCAPP data transfer started")
Class for data transfer between the card and the host using the SCAPP API.
59 def __init__(self, card : Card, direction : Direction = Direction.Acquisition): 60 if not _cuda_support: 61 raise ImportError("CUDA support is not available. Please install the cupy and cuda-python packages.") 62 super().__init__(card) 63 scapp_feature = bool(self.card._features & SPCM_FEAT_SCAPP) 64 if not scapp_feature: 65 raise SpcmException(text="The card does not have the SCAPP option installed. SCAPP is a add-on feature that needs to be bought separately, please contact info@spec.de and ask for the SCAPP option for the card with serial number {}".format(self.card.sn())) 66 self.direction = direction 67 self.iterator_index = 0
Initialize the DataTransfer object with a card object and additional arguments
Parameters
- card (Card): the card object that is used for the data transfer
- *args (list): list of additional arguments
- **kwargs (dict): dictionary of additional keyword arguments
69 def allocate_buffer(self, num_samples : int) -> None: 70 """ 71 Memory allocation for the buffer that is used for communicating with the card 72 73 Parameters 74 ---------- 75 num_samples : int | pint.Quantity = None 76 use the number of samples an get the number of active channels and bytes per samples directly from the card 77 """ 78 79 self.buffer_samples = UnitConversion.convert(num_samples, units.Sa, int) 80 # Allocate RDMA buffer 81 self.buffer = cp.empty((self.num_channels, self.buffer_samples), dtype = self.numpy_type(), order='F') 82 flag = 1 83 checkCudaErrors(cuda.cuPointerSetAttribute(flag, cuda.CUpointer_attribute.CU_POINTER_ATTRIBUTE_SYNC_MEMOPS, self.buffer.data.ptr))
Memory allocation for the buffer that is used for communicating with the card
Parameters
- num_samples (int | pint.Quantity = None): use the number of samples an get the number of active channels and bytes per samples directly from the card
85 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: 86 """ 87 Setup an RDMA transfer. 88 89 Parameters 90 ---------- 91 *args : list 92 Additional commands that are send to the card. 93 direction : int 94 the direction of the transfer 95 notify_samples : int 96 Size of the part of the buffer that is used for notifications. 97 transfer_offset : int 98 the offset of the transfer 99 transfer_length : int 100 Total length of the transfer buffer. 101 """ 102 103 # only change this locally 104 if notify_samples is not None: 105 notify_size = notify_samples * self.num_channels * self.bytes_per_sample 106 else: 107 notify_size = self.notify_size 108 transfer_offset = UnitConversion.convert(transfer_offset, units.Sa, int) 109 transfer_length = UnitConversion.convert(transfer_length, units.Sa, int) 110 111 if self.buffer is None: 112 raise SpcmException(text="No buffer defined for transfer") 113 if buffer_type: 114 self.buffer_type = buffer_type 115 if direction is None: 116 if self.direction == Direction.Acquisition: 117 direction = SPCM_DIR_CARDTOGPU 118 elif self.direction == Direction.Generation: 119 direction = SPCM_DIR_GPUTOCARD 120 else: 121 raise SpcmException(text="Please define a direction for transfer (SPCM_DIR_CARDTOGPU or SPCM_DIR_GPUTOCARD)") 122 123 if self._notify_samples != 0 and np.remainder(self.buffer_samples, self._notify_samples) and exception_num_samples: 124 raise SpcmException("The number of samples needs to be a multiple of the notify samples.") 125 126 if transfer_offset: 127 transfer_offset_bytes = self.samples_to_bytes(transfer_offset) 128 else: 129 transfer_offset_bytes = 0 130 131 self.buffer_samples = transfer_length 132 133 # we define the buffer for transfer 134 self.card._print("Starting the DMA transfer and waiting until data is in board memory") 135 self.card._check_error(spcm_dwDefTransfer_i64(self.card._handle, self.buffer_type, direction, notify_size, c_void_p(self.buffer.data.ptr), transfer_offset_bytes, self.buffer_size)) 136 137 # Execute additional commands if available 138 if args: 139 cmd = 0 140 for arg in args: 141 cmd |= arg 142 self.card.cmd(cmd) 143 self.card._print("... SCAPP data transfer started")
Setup an RDMA transfer.
Parameters
- *args (list): Additional commands that are send to the card.
- direction (int): the direction of the transfer
- notify_samples (int): Size of the part of the buffer that is used for notifications.
- transfer_offset (int): the offset of the transfer
- transfer_length (int): Total length of the transfer buffer.
17class SynchronousDigitalIOs(MultiPurposeIOs): 18 """a higher-level abstraction of the CardFunctionality class to implement the Card's synchronuous digital I/O functionality""" 19 20 data_transfer : DataTransfer = None 21 channels : Channels = None 22 buffer : npt.NDArray = None 23 item_size : int = None 24 connections : dict = {} 25 lowest_used_channel_bit : dict = {} 26 setups : dict = {} 27 channel_mask = {} 28 29 def __init__(self, data_transfer, channels, digin2bit : bool = False, *args, **kwargs) -> None: 30 """ 31 Constructor for the SynchronousDigitalIOs class 32 33 Parameters 34 ---------- 35 data_transfer : DataTransfer 36 The card object to communicate with the card 37 channels : Channels 38 The channels of the card 39 digin2bit : bool = False (44xx only) 40 If True, the digital input bits will be encoded as the 2 highest valued bits of the available channels 41 """ 42 43 self.data_transfer = data_transfer 44 self.card = data_transfer.card 45 self.channels = channels 46 self.item_size = data_transfer.bytes_per_sample 47 self.lowest_used_channel_bit = {} 48 for channel in self.channels: 49 self.lowest_used_channel_bit[int(channel)] = self.item_size * 8 50 self.channel_mask[int(channel)] = 0 51 self.connections = {} 52 self.setups = {} 53 self._max_buffer_index = -1 54 self._buffer_iterator_index = -1 55 self.num_xio_lines = self.get_num_xio_lines() 56 57 # For the 22xx and 23xx families there are only fixed xio lines possible 58 if self.card.family() == 0x22 or self.card.family() == 0x23 or self.card.family() == 0x44: 59 digin2bit &= (self.card.family() == 0x44) 60 if digin2bit: 61 self.x_mode(0, SPCM_XMODE_DIGIN2BIT) 62 else: 63 self.x_mode(0, SPCM_XMODE_DIGIN) # Turn-on one line turns on all the xio lines 64 num_channels = len(channels) 65 if digin2bit: 66 num_channels *= 2 67 num_buffers = np.min([num_channels, self.num_xio_lines]) 68 self._allocate_buffer(num_buffers) 69 xios = [0,1,2,0] 70 xios2 = [1,2,0,1] 71 if (self.card.family() == 0x22 or self.card.family() == 0x23) and (self.card.card_type() & TYP_CHMASK == 0x3 or self.card.card_type() & TYP_CHMASK == 0x1): 72 if self.card.get_i(SPC_SAMPLERATE) > 1.25e9: # There is a bug in the 22xx family with one channel cards, see page 132 in the manual 73 print("Warning: this is the bug in the 22xx family with one channel cards, see page 132 in the manual") 74 self.lowest_used_channel_bit[1] -= 1 75 self.connections[0] = {'channel': 1, 'bit': self.lowest_used_channel_bit[1], 'xios': [0]} 76 self.lowest_used_channel_bit[0] -= 1 77 self.connections[1] = {'channel': 0, 'bit': self.lowest_used_channel_bit[0], 'xios': [2]} 78 else: 79 self.lowest_used_channel_bit[0] -= 1 80 self.connections[0] = {'channel': 0, 'bit': self.lowest_used_channel_bit[0], 'xios': [0]} 81 self.lowest_used_channel_bit[1] -= 1 82 self.connections[1] = {'channel': 1, 'bit': self.lowest_used_channel_bit[1], 'xios': [2]} 83 else: 84 for channel in self.channels: 85 self.lowest_used_channel_bit[int(channel)] -= 1 86 self.connections[xios[int(channel)]] = {'channel': int(channel), 'bit': self.lowest_used_channel_bit[int(channel)], 'xios': [xios[int(channel)]]} 87 if digin2bit: 88 self.lowest_used_channel_bit[int(channel)] -= 1 89 self.connections[xios2[int(channel)]] = {'channel': int(channel), 'bit': self.lowest_used_channel_bit[int(channel)], 'xios': [xios2[int(channel)]]} 90 pass 91 92 def __str__(self) -> str: 93 """ 94 String representation of the SynchronousDigitalIO class 95 96 Returns 97 ------- 98 str 99 String representation of the SynchronousDigitalIO class 100 """ 101 102 return f"SynchronousDigitalIOs(data_transfer={self.data_transfer})" 103 104 __repr__ = __str__ 105 106 def __len__(self) -> int: 107 """ 108 Get the number of buffers 109 110 Returns 111 ------- 112 int 113 The number of buffers 114 """ 115 116 return self._num_buffers 117 118 _buffer_iterator_index = -1 119 def __iter__(self) -> "SynchronousDigitalIOs": 120 """Define this class as an iterator""" 121 self._buffer_iterator_index = -1 122 return self 123 124 def __getitem__(self, index : int): 125 """ 126 Get the buffer at the specified index 127 128 Parameters 129 ---------- 130 index : int 131 buffer index 132 """ 133 134 return self.buffer[index, :] 135 136 def __next__(self) -> MultiPurposeIO: 137 """ 138 This method is called when the next element is requested from the iterator 139 140 Returns 141 ------- 142 MultiPurposeIO 143 The next xio line in the iterator 144 145 Raises 146 ------ 147 StopIteration 148 """ 149 self._buffer_iterator_index += 1 150 if self._buffer_iterator_index >= self._num_buffers: 151 self._buffer_iterator_index = -1 152 raise StopIteration 153 return self.buffer[self._buffer_iterator_index, :] 154 155 def current_xio(self) -> int: 156 """ 157 Get the current xio line 158 159 Returns 160 ------- 161 int 162 current xio line 163 """ 164 165 return self.connections[self._buffer_iterator_index]['xios'][0] 166 167 def x_mode(self, xio : int, mode : int = None) -> int: 168 """ 169 Sets the mode of the digital input/output of the card (see register 'SPCM_X0_MODE' in chapter `Multi Purpose I/O Lines` in the manual) 170 171 Parameters 172 ---------- 173 mode : int 174 The mode of the digital input/output 175 176 Returns 177 ------- 178 int 179 The mode of the digital input/output 180 """ 181 182 if mode is not None: 183 self.card.set_i(SPCM_X0_MODE + xio, mode) 184 return self.card.get_i(SPCM_X0_MODE + xio) 185 186 def digmode(self, channel : int, mode : int = None) -> int: 187 """ 188 Sets the mode of the digital input of the card (see register 'SPC_DIGMODE0' in chapter `Synchronous digital inputs` in the manual) 189 190 Parameters 191 ---------- 192 mode : int 193 The mode of the digital input 194 195 Returns 196 ------- 197 int 198 The mode of the digital input 199 """ 200 201 if mode is not None: 202 self.card.set_i(SPC_DIGMODE0 + channel, mode) 203 return self.card.get_i(SPC_DIGMODE0 + channel) 204 205 def allocate_buffer(self, num_buffers : int) -> npt.NDArray: 206 """ 207 Allocate the buffers for the digital input/output lines of the card 208 209 Parameters 210 ---------- 211 num_buffers : int 212 The number of buffers to allocate 213 """ 214 215 if self.card.family() == 0x22 or self.card.family() == 0x23 or self.card.family() == 0x44: 216 print("The 22xx, 23xx and 44xx families only support fixed xio lines, allocate_buffer() is not necessary and doesn't change the system") 217 else: 218 self._allocate_buffer(num_buffers) 219 return self.buffer 220 221 def _allocate_buffer(self, num_buffers : int) -> npt.NDArray: 222 """ 223 Allocate the buffers for the digital input/output lines of the card 224 225 Parameters 226 ---------- 227 num_buffers : int 228 The number of buffers to allocate 229 """ 230 231 self._num_buffers = num_buffers 232 # TODO make this usable with multiple replay/recording 233 self.buffer = np.zeros((self._num_buffers, self.data_transfer.buffer.shape[-1]), dtype=self.data_transfer.numpy_type()) 234 return self.buffer 235 236 def setup(self, buffer_index : int, channel, xios : list) -> None: 237 """ 238 Register the connection of a buffer with the corresponding bit in a specific channel of the card 239 240 Parameters 241 ---------- 242 buffer_index : int 243 The index of the buffer to be used 244 channel : Channel | int 245 The channel index 246 xios : list | int 247 The xio lines that the buffer is connected to 248 """ 249 250 if self.card.family() == 0x22 or self.card.family() == 0x23 or self.card.family() == 0x44: 251 print("The 22xx, 23xx and 44x family only support fixed xio lines, setup() is not necessary and doesn't change the system") 252 return 253 254 # Define the buffer 255 if isinstance(xios, int): 256 xios = [xios] 257 self.lowest_used_channel_bit[int(channel)] -= 1 258 self.connections[buffer_index] = {'channel': int(channel), 'bit': self.lowest_used_channel_bit[int(channel)], 'xios': xios} 259 260 # Setup the right xio mode 261 if self.data_transfer.direction == Direction.Generation: 262 bit_mask = SPCM_XMODE_DIGOUTSRC_BIT15 << (15 - self.lowest_used_channel_bit[int(channel)]) 263 channel_mask = SPCM_XMODE_DIGOUTSRC_CH0 << int(channel) 264 for xio in xios: 265 self.x_mode(xio, SPCM_XMODE_DIGOUT | channel_mask | bit_mask) 266 elif self.data_transfer.direction == Direction.Acquisition: 267 bit_mask = DIGMODEMASK_BIT15 >> 5*(15 - self.lowest_used_channel_bit[int(channel)]) 268 x_mask = SPCM_DIGMODE_X0 + (xios[0]) * (SPCM_DIGMODE_X1 - SPCM_DIGMODE_X0) 269 self.channel_mask[int(channel)] |= bit_mask & x_mask 270 self.x_mode(xios[0], SPCM_XMODE_DIGIN) 271 self.digmode(int(channel), self.channel_mask[int(channel)]) 272 else: 273 raise SpcmException(text="Please specify a data transfer direction: (Acquisition or Generation)") 274 275 def process(self, no_shift : bool = False): 276 """ 277 Process the digital input/output lines of the card 278 279 Parameters 280 ---------- 281 no_shift : bool 282 If True, no bit shift will be applied 283 """ 284 285 itemsize_bits = self.item_size * 8 286 uint_type = self._int2uint(self.data_transfer.numpy_type()) 287 if self.data_transfer.direction == Direction.Generation: 288 if not no_shift: 289 for channel, lowest_bit in self.lowest_used_channel_bit.items(): 290 bit_shift = itemsize_bits - lowest_bit 291 self.data_transfer.buffer[channel, :] = (self.data_transfer.buffer[channel, :].view(uint_type) >> bit_shift) 292 293 for key, connection in self.connections.items(): 294 self.data_transfer.buffer[connection['channel'], :] |= self.buffer[key, :] << connection['bit'] 295 elif self.data_transfer.direction == Direction.Acquisition: 296 for buffer_index in range(self._num_buffers): 297 channel_index = int(self.connections[buffer_index]['channel']) 298 self.buffer[buffer_index, :] = (self.data_transfer.buffer[channel_index, :] >> self.connections[buffer_index]['bit']) & 0x1 299 300 if not no_shift: 301 for channel, lowest_bit in self.lowest_used_channel_bit.items(): 302 bit_shift = itemsize_bits - lowest_bit 303 self.data_transfer.buffer[channel, :] = (self.data_transfer.buffer[channel, :].view(uint_type) << bit_shift) 304 else: 305 raise SpcmException(text="Please specify a data transfer direction: (Acquisition or Generation)") 306 307 def _int2uint(self, np_type : npt.DTypeLike) -> npt.DTypeLike: 308 """ 309 Convert the integer data to unsigned integer data 310 """ 311 312 if np_type == np.int8: 313 return np.uint8 314 elif np_type == np.int16: 315 return np.uint16 316 elif np_type == np.int32: 317 return np.uint32 318 elif np_type == np.int64: 319 return np.uint64 320 else: 321 raise SpcmException(text="The data type is not supported for signed to unsigned conversion")
a higher-level abstraction of the CardFunctionality class to implement the Card's synchronuous digital I/O functionality
29 def __init__(self, data_transfer, channels, digin2bit : bool = False, *args, **kwargs) -> None: 30 """ 31 Constructor for the SynchronousDigitalIOs class 32 33 Parameters 34 ---------- 35 data_transfer : DataTransfer 36 The card object to communicate with the card 37 channels : Channels 38 The channels of the card 39 digin2bit : bool = False (44xx only) 40 If True, the digital input bits will be encoded as the 2 highest valued bits of the available channels 41 """ 42 43 self.data_transfer = data_transfer 44 self.card = data_transfer.card 45 self.channels = channels 46 self.item_size = data_transfer.bytes_per_sample 47 self.lowest_used_channel_bit = {} 48 for channel in self.channels: 49 self.lowest_used_channel_bit[int(channel)] = self.item_size * 8 50 self.channel_mask[int(channel)] = 0 51 self.connections = {} 52 self.setups = {} 53 self._max_buffer_index = -1 54 self._buffer_iterator_index = -1 55 self.num_xio_lines = self.get_num_xio_lines() 56 57 # For the 22xx and 23xx families there are only fixed xio lines possible 58 if self.card.family() == 0x22 or self.card.family() == 0x23 or self.card.family() == 0x44: 59 digin2bit &= (self.card.family() == 0x44) 60 if digin2bit: 61 self.x_mode(0, SPCM_XMODE_DIGIN2BIT) 62 else: 63 self.x_mode(0, SPCM_XMODE_DIGIN) # Turn-on one line turns on all the xio lines 64 num_channels = len(channels) 65 if digin2bit: 66 num_channels *= 2 67 num_buffers = np.min([num_channels, self.num_xio_lines]) 68 self._allocate_buffer(num_buffers) 69 xios = [0,1,2,0] 70 xios2 = [1,2,0,1] 71 if (self.card.family() == 0x22 or self.card.family() == 0x23) and (self.card.card_type() & TYP_CHMASK == 0x3 or self.card.card_type() & TYP_CHMASK == 0x1): 72 if self.card.get_i(SPC_SAMPLERATE) > 1.25e9: # There is a bug in the 22xx family with one channel cards, see page 132 in the manual 73 print("Warning: this is the bug in the 22xx family with one channel cards, see page 132 in the manual") 74 self.lowest_used_channel_bit[1] -= 1 75 self.connections[0] = {'channel': 1, 'bit': self.lowest_used_channel_bit[1], 'xios': [0]} 76 self.lowest_used_channel_bit[0] -= 1 77 self.connections[1] = {'channel': 0, 'bit': self.lowest_used_channel_bit[0], 'xios': [2]} 78 else: 79 self.lowest_used_channel_bit[0] -= 1 80 self.connections[0] = {'channel': 0, 'bit': self.lowest_used_channel_bit[0], 'xios': [0]} 81 self.lowest_used_channel_bit[1] -= 1 82 self.connections[1] = {'channel': 1, 'bit': self.lowest_used_channel_bit[1], 'xios': [2]} 83 else: 84 for channel in self.channels: 85 self.lowest_used_channel_bit[int(channel)] -= 1 86 self.connections[xios[int(channel)]] = {'channel': int(channel), 'bit': self.lowest_used_channel_bit[int(channel)], 'xios': [xios[int(channel)]]} 87 if digin2bit: 88 self.lowest_used_channel_bit[int(channel)] -= 1 89 self.connections[xios2[int(channel)]] = {'channel': int(channel), 'bit': self.lowest_used_channel_bit[int(channel)], 'xios': [xios2[int(channel)]]} 90 pass
Constructor for the SynchronousDigitalIOs class
Parameters
- data_transfer (DataTransfer): The card object to communicate with the card
- channels (Channels): The channels of the card
- digin2bit (bool = False (44xx only)): If True, the digital input bits will be encoded as the 2 highest valued bits of the available channels
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() == 0x22 or self.card.family() == 0x23 or self.card.family() == 0x44: 216 print("The 22xx, 23xx and 44xx families only support fixed xio lines, allocate_buffer() is not necessary and doesn't change the system") 217 else: 218 self._allocate_buffer(num_buffers) 219 return self.buffer
Allocate the buffers for the digital input/output lines of the card
Parameters
- num_buffers (int): The number of buffers to allocate
236 def setup(self, buffer_index : int, channel, xios : list) -> None: 237 """ 238 Register the connection of a buffer with the corresponding bit in a specific channel of the card 239 240 Parameters 241 ---------- 242 buffer_index : int 243 The index of the buffer to be used 244 channel : Channel | int 245 The channel index 246 xios : list | int 247 The xio lines that the buffer is connected to 248 """ 249 250 if self.card.family() == 0x22 or self.card.family() == 0x23 or self.card.family() == 0x44: 251 print("The 22xx, 23xx and 44x family only support fixed xio lines, setup() is not necessary and doesn't change the system") 252 return 253 254 # Define the buffer 255 if isinstance(xios, int): 256 xios = [xios] 257 self.lowest_used_channel_bit[int(channel)] -= 1 258 self.connections[buffer_index] = {'channel': int(channel), 'bit': self.lowest_used_channel_bit[int(channel)], 'xios': xios} 259 260 # Setup the right xio mode 261 if self.data_transfer.direction == Direction.Generation: 262 bit_mask = SPCM_XMODE_DIGOUTSRC_BIT15 << (15 - self.lowest_used_channel_bit[int(channel)]) 263 channel_mask = SPCM_XMODE_DIGOUTSRC_CH0 << int(channel) 264 for xio in xios: 265 self.x_mode(xio, SPCM_XMODE_DIGOUT | channel_mask | bit_mask) 266 elif self.data_transfer.direction == Direction.Acquisition: 267 bit_mask = DIGMODEMASK_BIT15 >> 5*(15 - self.lowest_used_channel_bit[int(channel)]) 268 x_mask = SPCM_DIGMODE_X0 + (xios[0]) * (SPCM_DIGMODE_X1 - SPCM_DIGMODE_X0) 269 self.channel_mask[int(channel)] |= bit_mask & x_mask 270 self.x_mode(xios[0], SPCM_XMODE_DIGIN) 271 self.digmode(int(channel), self.channel_mask[int(channel)]) 272 else: 273 raise SpcmException(text="Please specify a data transfer direction: (Acquisition or Generation)")
Register the connection of a buffer with the corresponding bit in a specific channel of the card
Parameters
- buffer_index (int): The index of the buffer to be used
- channel (Channel | int): The channel index
- xios (list | int): The xio lines that the buffer is connected to
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