/spectrumdevice

Python library for communicating with digitisers manufactured by Spectrum Instrumentation

Primary LanguagePythonMIT LicenseMIT

spectrumdevice

A high-level, object-oriented Python library for controlling Spectrum Instrumentation devices.

spectrumdevice can connect to individual cards or StarHubs (e.g. the NetBox). spectrumdevice provides the following classes for controlling devices:

Name Purpose
SpectrumDigitiserCard Controlling individual digitiser cards
SpectrumDigitiserStarHub Controlling digitiser cards aggregated with a StarHub
SpectrumAWGCard Controlling individual AWG cards
SpectrumAWGStarHub Controlling AWG cards aggregated with a StarHub

spectrumdevice also includes mock classes for testing software without drivers installed or hardware connected:

Name Purpose
MockSpectrumDigitiserCard Mocking individual digitiser cards
MockSpectrumDigitiserStarHub Mocking digitiser cards aggregated with a StarHub
MockSpectrumAWGCard Mocking individual AWG cards
MockSpectrumAWGStarHub Mocking AWG cards aggregated with a StarHub

For digitisers, spectrumdevice currently only supports 'Standard Single' and 'Multi FIFO' acquisition modes. See the Limitations section for more information.

Requirements

spectrumdevice works with hardware on Windows and Linux. Spectrum do not currently provide a hardware driver for macOS, but spectrumdevice provides mock classes for development and testing without hardware, which work on macOS.

To work with hardware, spectrumdevice requires that you have installed the Spectrum driver for your platform. On Windows, this should be located at c:\windows\system32\spcm_win64.dll (or spcm_win32.dll on a 32-bit system). On Linux, it will be called libspcm_linux.so. If no driver is present spectrumdevice can still run in mock mode.

Installation and dependencies

To install the latest release using pip:

pip install spectrumdevice

To install the latest release using conda:

conda install -c conda-forge spectrumdevice

To install the development version:

pip install https://github.com/KCL-BMEIS/spectrumdevice/tarball/main.

spectrumdevice depends only on NumPy. spectrumdevice includes a module called spectrum_gmbh containing a few files taken from the spcm_examples directory, provided with Spectrum hardware. The files in this module were written by Spectrum GMBH and are included with their permission. The files provide spectrumdevice with a low-level Python interface to the Spectrum driver and define global constants which are used throughout spectrumdevice.

Limitations

  • Currently, spectrumdevice only supports Standard Single and Multi FIFO digitiser acquisition modes. See the Spectrum documentation for more information.
  • When defining a transfer buffer - the software buffer into which samples are transferred between a hardware device - and Python - the notify-size is automatically set equal to the buffer length. This works fine for most situations. See the Spectrum documentation for more information.
  • If timestamping is enabled, timestamps are acquired using Spectrum's 'polling' mode. This seems to add around 5 to 10 ms of latency to the acquisition.
  • Only current digitisers from the 59xx, 44xx and 22xx families are currently supported, and spectrumdevice has only been tested on 59xx devices. However, spectrumdevice may work fine on older devices. If you've tried spectrumdevice on an older device, please let us know if it works and raise any issues you encounter in the issue tracker. It's likely possible to add support with minimal effort.

todo: add supported AWG devices

Usage

Connect to devices

Connect to local (PCIe) cards:

from spectrumdevice import SpectrumDigitiserCard

card_0 = SpectrumDigitiserCard(device_number=0)
card_1 = SpectrumDigitiserCard(device_number=1)

Connect to networked cards (you can find a card's IP using the Spectrum Control Centre software):

from spectrumdevice import SpectrumDigitiserCard

card_0 = SpectrumDigitiserCard(device_number=0, ip_address="192.168.0.2")
card_1 = SpectrumDigitiserCard(device_number=0, ip_address="192.168.0.3")

Connect to a networked StarHub (e.g. a NetBox).

from spectrumdevice import SpectrumDigitiserCard, SpectrumDigitiserStarHub

NUM_CARDS_IN_STAR_HUB = 2
STAR_HUB_MASTER_CARD_INDEX = 1  # The card controlling the clock
HUB_IP_ADDRESS = "192.168.0.2"

# Connect to each card in the hub.
child_cards = []
for n in range(NUM_CARDS_IN_STAR_HUB):
  child_cards.append(SpectrumDigitiserCard(device_number=n, ip_address=HUB_IP_ADDRESS))

# Connect to the hub itself
hub = SpectrumDigitiserStarHub(device_number=0, child_cards=child_cards,
                               master_card_index=STAR_HUB_MASTER_CARD_INDEX)

Once connected, a SpectrumStarHub object can be configured and used in exactly the same way as a SpectrumCard object — commands will be sent to the child cards automatically.

Using Mock Devices

You can use mock devices to test your software without hardware connected or drivers installed. After construction, Mock devices have the same interface as real devices. Mock digitisers provide random waveforms generated by an internal mock data source. The number of channels and modules in a mock card must be provided on construction as shown below. You can match these values to your hardware by inspecting the number of channels and modules in a hardware device using the Spectrum Control Centre software. The frame rate of the mock data source must also be set on construction.

from spectrumdevice import MockSpectrumDigitiserCard, MockSpectrumDigitiserStarHub
from spectrumdevice.settings import CardType

mock_card = MockSpectrumDigitiserCard(device_number=0, card_type=CardType.TYP_M2P5966_X4,
                                      mock_source_frame_rate_hz=10.0,
                                      num_modules=2, num_channels_per_module=4)
mock_hub = MockSpectrumDigitiserStarHub(device_number=0, child_cards=[mock_card], master_card_index=0)

After construction, mock devices can be used identically to real devices.

Configuring device settings

SpectrumDigitiserCard and SpectrumDigitiserStarHub provide methods for reading and writing device settings located within on-device registers. Some settings must be set using Enums imported from the settings module. Others are set using integer values. For example, to put a card in 'Standard Single' acquisition mode and set the sample rate to 10 MHz:

from spectrumdevice import SpectrumDigitiserCard
from spectrumdevice.settings import AcquisitionMode

card = SpectrumDigitiserCard(device_number=0)
card.set_acquisition_mode(AcquisitionMode.SPC_REC_STD_SINGLE)
card.set_sample_rate_in_hz(10000000)

and to print the currently set sample rate:

print(card.sample_rate_in_hz)

Configuring channel settings

The channels available to a spectrum device (card or StarHub) can be accessed via the channels property. This property contains a list of SpectrumDigitiserChannel or SpectrumAWGChannel objects which provide methods for independently configuring each channel. For example, to change the vertical range of channel 2 of a digitiser card to 1V:

card.channels[2].set_vertical_range_in_mv(1000)

and then print the vertical offset:

print(card.channels[2].vertical_offset_in_percent)

Configuring everything at once

You can set multiple settings at once using the TriggerSettings and AcquisitionSettings dataclasses and the configure_trigger() and configure_acquisition() methods:

from spectrumdevice.settings import TriggerSettings, AcquisitionSettings, TriggerSource, ExternalTriggerMode, \
AcquisitionMode

trigger_settings = TriggerSettings(
  trigger_sources=[TriggerSource.SPC_TMASK_EXT0],
  external_trigger_mode=ExternalTriggerMode.SPC_TM_POS,
  external_trigger_level_in_mv=1000,
)

acquisition_settings = AcquisitionSettings(
  acquisition_mode=AcquisitionMode.SPC_REC_FIFO_MULTI,
  sample_rate_in_hz=40000000,
  acquisition_length_in_samples=400,
  pre_trigger_length_in_samples=0,
  timeout_in_ms=1000,
  enabled_channels=[0, 1, 2, 3],
  vertical_ranges_in_mv=[200, 200, 200, 200],
  vertical_offsets_in_percent=[0, 0, 0, 0],
  timestamping_enabled=True
)

card.configure_trigger(trigger_settings)
card.configure_acquisition(acquisition_settings)

Acquiring waveforms from a digitiser (standard single mode)

To acquire data in standard single mode, place the device into the correct mode using configure_acquisition() or card.set_acquisition_mode(AcquisitionMode.SPC_REC_STD_SINGLE) and then execute the acquisition:

measurement = card.execute_standard_single_acquisition()

measurement is a Measurement dataclass containing the waveforms received by each enabled channel and, if timestamping was enabled in the AcquisitionSettings, the time at which the acquisition was triggered:

waveforms = measurement.waveforms  # A list of 1D numpy arrays
timestamp = measurement.timestamp  # A datetime.datetime object

Acquiring waveforms from a digitiser (FIFO mode)

To acquire data in FIFO mode, place the device into the correct mode using configure_acquisition() or card.set_acquisition_mode(AcquisitionMode.SPC_REC_FIFO_MULTI).

You can then carry out a predefined number of Multi FIFO measurements like this:

NUM_MEASUREMENTS = 2
measurements = card.execute_finite_fifo_acquisition(NUM_MEASUREMENTS)

measurements will be a list of Measurement dataclasses (of length NUM_MEASUREMENTS), where each Measurement object contains the waveforms received by each enabled channel during a measurement.

Alternatively, you can start a Multi FIFO acquisition continuously writing data to a software 'transfer' buffer:

card.execute_continuous_fifo_acquisition()

But you'll then need to pull the data out of the transfer buffer at least as fast as the data is being acquired, manually obtaining the waveforms and timestamp:

measurements_list = []
while True:
    measurements_list.append(Measurement(waveforms=card.get_waveforms(),
                                         timestamp=card.get_timestamp()))

Each call to get_waveforms() will wait until the next set of waveform data is available. When ready, you'll need to stop the acquisition:

card.stop()

and execute some logic to exit the while loop.

Examples

See the example_scripts directory.

API Documentation

See here for documentation for the complete API.