Skip to content

Commit

Permalink
Merge pull request #31 from KCL-BMEIS/30_notify_size_no_awg
Browse files Browse the repository at this point in the history
Implement a notify size for faster acquisition, enabling bulk acquisitions
  • Loading branch information
crnbaker authored Oct 3, 2023
2 parents e98acd9 + 957a6fd commit a9c1d3a
Show file tree
Hide file tree
Showing 36 changed files with 11,152 additions and 316 deletions.
62 changes: 42 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
# spectrumdevice
from spectrumdevice.settings.transfer_buffer import BufferTypefrom spectrumdevice.settings.transfer_buffer import transfer_buffer_factoryfrom spectrumdevice.settings.transfer_buffer import transfer_buffer_factory# spectrumdevice
A high-level, object-oriented Python library for controlling Spectrum Instrumentation devices.

`spectrumdevice` can connect to individual cards or
[StarHubs](https://spectrum-instrumentation.com/en/m4i-star-hub) (e.g. the
[NetBox](https://spectrum-instrumentation.com/en/digitizernetbox)). `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 |
| Name | Purpose |
|----------------------------|-----------------------------------------------------------------------|
| `SpectrumDigitiserCard` | Controlling individual digitiser cards |
| `SpectrumDigitiserStarHub` | Controlling digitiser cards aggregated with a StarHub |
| `SpectrumAWGCard` | Controlling individual AWG cards (Not yet implemented) |
| `SpectrumAWGStarHub` | Controlling AWG cards aggregated with a StarHub (Not yet implemented) |

`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 |
| Name | Purpose |
|--------------------------------|-------------------------------------------------------------------|
| `MockSpectrumDigitiserCard` | Mocking individual digitiser cards |
| `MockSpectrumDigitiserStarHub` | Mocking digitiser cards aggregated with a StarHub |
| `MockSpectrumAWGCard` | Mocking individual AWG cards (Not yet implemented) |
| `MockSpectrumAWGStarHub` | Mocking AWG cards aggregated with a StarHub (Not yet implemented) |

For digitisers, `spectrumdevice` currently only supports 'Standard Single' and 'Multi FIFO' acquisition modes. See the
Limitations section for more information.
Expand Down Expand Up @@ -59,9 +59,6 @@ files taken from the `spcm_examples` directory, provided with Spectrum hardware.
## 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](https://spectrum-instrumentation.com/de/59xx-16-bit-digitizer-125-mss),
Expand All @@ -70,7 +67,6 @@ files taken from the `spcm_examples` directory, provided with Spectrum hardware.
`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
Expand Down Expand Up @@ -124,9 +120,9 @@ of the mock data source must also be set on construction.

```python
from spectrumdevice import MockSpectrumDigitiserCard, MockSpectrumDigitiserStarHub
from spectrumdevice.settings import CardType
from spectrumdevice.settings import ModelNumber

mock_card = MockSpectrumDigitiserCard(device_number=0, card_type=CardType.TYP_M2P5966_X4,
mock_card = MockSpectrumDigitiserCard(device_number=0, model=ModelNumber.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)
Expand Down Expand Up @@ -212,7 +208,33 @@ 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)`.
card.set_acquisition_mode(AcquisitionMode.SPC_REC_FIFO_MULTI)`. You can then also construct your own
`TransferBuffer` object and provide it to card using the `define_transfer_buffer()` method:

```python
from spectrumdevice.settings.transfer_buffer import (
BufferDirection,
BufferType,
transfer_buffer_factory,
)

size_in_samples = 100
board_memory_offset_bytes = 0
notify_size_in_pages = 10

buffer = transfer_buffer_factory(
buffer_type=BufferType.SPCM_BUF_DATA, # must be SPCM_BUF_DATA to transfer samples from digitiser
direction=BufferDirection.SPCM_DIR_CARDTOPC, # must be SPCM_DIR_CARDTOPC to transfer samples from digitiser
size_in_samples=size_in_samples,
board_memory_offset_bytes=board_memory_offset_bytes,
notify_size_in_pages=notify_size_in_pages
)

card.define_transfer_buffer(buffer)
```
this allows you to set your own transfer buffer size and notify size. If you do not call `define_transfer_buffer()` yourself,
then a default transfer buffer will be used, which will have a notify size of 10 pages (40 kB) and will be large
enough to hold 1000 repeat acquisitions without overflowing.

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

Expand Down
227 changes: 227 additions & 0 deletions docs/contents.html

Large diffs are not rendered by default.

6,779 changes: 6,779 additions & 0 deletions docs/index.html

Large diffs are not rendered by default.

36 changes: 36 additions & 0 deletions docs/search.js

Large diffs are not rendered by default.

3,260 changes: 3,260 additions & 0 deletions docs/spectrumdevice/settings.html

Large diffs are not rendered by default.

13 changes: 7 additions & 6 deletions src/example_scripts/connect_to_star_hub.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
from spectrumdevice.devices.mocks import MockSpectrumDigitiserCard, MockSpectrumDigitiserStarHub
from spectrumdevice.devices.digitiser import SpectrumDigitiserCard
from spectrumdevice.devices.digitiser import SpectrumDigitiserStarHub
from spectrumdevice.settings import CardType
from spectrumdevice.settings import ModelNumber


def star_hub_example(mock_mode: bool, num_cards: int, master_card_index: int) -> SpectrumDigitiserStarHub:
def star_hub_example(
mock_mode: bool, num_cards: int, master_card_index: int, ip_address: str
) -> SpectrumDigitiserStarHub:

if not mock_mode:
DEVICE_IP_ADDRESS = "169.254.142.75"
child_cards = []
for n in range(num_cards):
# Connect to each card in the hub.
child_cards.append(SpectrumDigitiserCard(device_number=n, ip_address=DEVICE_IP_ADDRESS))
child_cards.append(SpectrumDigitiserCard(device_number=n, ip_address=ip_address))
# Connect to the hub itself
return SpectrumDigitiserStarHub(device_number=0, child_cards=child_cards, master_card_index=master_card_index)
else:
Expand All @@ -21,7 +22,7 @@ def star_hub_example(mock_mode: bool, num_cards: int, master_card_index: int) ->
mock_child_cards.append(
MockSpectrumDigitiserCard(
device_number=n,
card_type=CardType.TYP_M2P5966_X4,
model=ModelNumber.TYP_M2P5966_X4,
mock_source_frame_rate_hz=10.0, # Mock devices need to be provided with a mock source frame rate
num_modules=2, # (For real devices, this and num_channels_per_module are read from the hardware).
num_channels_per_module=4,
Expand All @@ -34,7 +35,7 @@ def star_hub_example(mock_mode: bool, num_cards: int, master_card_index: int) ->


if __name__ == "__main__":
hub = star_hub_example(mock_mode=True, num_cards=2, master_card_index=1)
hub = star_hub_example(mock_mode=True, num_cards=2, master_card_index=1, ip_address="169.254.45.181")
print(f"{hub} contains {len(hub.channels)} channels in total:")
for channel in hub.channels:
print(channel)
Expand Down
58 changes: 32 additions & 26 deletions src/example_scripts/continuous_averaging_fifo_mode.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from spectrumdevice.measurement import Measurement
from spectrumdevice.settings import (
AcquisitionMode,
CardType,
ModelNumber,
TriggerSource,
ExternalTriggerMode,
TriggerSettings,
Expand All @@ -23,6 +23,7 @@ def continuous_averaging_multi_fifo_example(
trigger_source: TriggerSource,
device_number: int,
ip_address: Optional[str] = None,
acquisition_length: int = 400,
) -> List[Measurement]:

if not mock_mode:
Expand All @@ -32,7 +33,7 @@ def continuous_averaging_multi_fifo_example(
# Set up a mock device
card = MockSpectrumDigitiserCard(
device_number=device_number,
card_type=CardType.TYP_M2P5966_X4,
model=ModelNumber.TYP_M2P5966_X4,
mock_source_frame_rate_hz=1.0,
num_modules=2,
num_channels_per_module=4,
Expand All @@ -49,7 +50,7 @@ def continuous_averaging_multi_fifo_example(
acquisition_settings = AcquisitionSettings(
acquisition_mode=AcquisitionMode.SPC_REC_FIFO_AVERAGE,
sample_rate_in_hz=40000000,
acquisition_length_in_samples=400,
acquisition_length_in_samples=acquisition_length,
pre_trigger_length_in_samples=0,
timeout_in_ms=1000,
enabled_channels=[0],
Expand All @@ -59,29 +60,34 @@ def continuous_averaging_multi_fifo_example(
number_of_averages=num_averages,
)

# Apply settings
card.configure_trigger(trigger_settings)
card.configure_acquisition(acquisition_settings)

# Execute acquisition
start_time = monotonic()
card.execute_continuous_fifo_acquisition()

# Retrieve streamed waveform data until desired time has elapsed
measurements_list = []
while (monotonic() - start_time) < acquisition_duration_in_seconds:
measurements_list.append(Measurement(waveforms=card.get_waveforms(), timestamp=card.get_timestamp()))
if measurements_list[-1].timestamp is not None:
print(
f"Got measurement triggered at {measurements_list[-1].timestamp.time()} (acquisition latency of"
f" {(datetime.datetime.now() - measurements_list[-1].timestamp).microseconds * 1e-3} ms)"
)

# Stop the acquisition (and streaming)
card.stop()

card.reset()
card.disconnect()
try:
# Apply settings
card.configure_trigger(trigger_settings)
card.configure_acquisition(acquisition_settings)

# Execute acquisition
card.execute_continuous_fifo_acquisition()
start_time = monotonic()
# Retrieve streamed waveform data until desired time has elapsed
measurements_list = []
while (monotonic() - start_time) < acquisition_duration_in_seconds:
measurements_list += [
Measurement(waveforms=frame, timestamp=card.get_timestamp()) for frame in card.get_waveforms()
]
print(f"got {measurements_list} measurements")
if measurements_list[-1].timestamp is not None:
print(
f"Got measurement triggered at {measurements_list[-1].timestamp.time()} (acquisition latency of"
f" {(datetime.datetime.now() - measurements_list[-1].timestamp).microseconds * 1e-3} ms)"
)

finally:
# Stop the acquisition (and streaming)
card.stop()

card.reset()
card.disconnect()

return measurements_list


Expand Down
70 changes: 37 additions & 33 deletions src/example_scripts/continuous_multi_fifo_mode.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from spectrumdevice.measurement import Measurement
from spectrumdevice.settings import (
AcquisitionMode,
CardType,
ModelNumber,
TriggerSource,
ExternalTriggerMode,
TriggerSettings,
Expand All @@ -22,6 +22,7 @@ def continuous_multi_fifo_example(
trigger_source: TriggerSource,
device_number: int,
ip_address: Optional[str] = None,
acquisition_length: int = 400,
) -> List[Measurement]:

if not mock_mode:
Expand All @@ -31,7 +32,7 @@ def continuous_multi_fifo_example(
# Set up a mock device
card = MockSpectrumDigitiserCard(
device_number=device_number,
card_type=CardType.TYP_M2P5966_X4,
model=ModelNumber.TYP_M2P5966_X4,
mock_source_frame_rate_hz=1.0,
num_modules=2,
num_channels_per_module=4,
Expand All @@ -48,38 +49,43 @@ def continuous_multi_fifo_example(
acquisition_settings = AcquisitionSettings(
acquisition_mode=AcquisitionMode.SPC_REC_FIFO_MULTI,
sample_rate_in_hz=40000000,
acquisition_length_in_samples=400,
acquisition_length_in_samples=acquisition_length,
pre_trigger_length_in_samples=0,
timeout_in_ms=1000,
timeout_in_ms=5000,
enabled_channels=[0],
vertical_ranges_in_mv=[200],
vertical_offsets_in_percent=[0],
timestamping_enabled=True,
)

# Apply settings
card.configure_trigger(trigger_settings)
card.configure_acquisition(acquisition_settings)

# Execute acquisition
start_time = monotonic()
card.execute_continuous_fifo_acquisition()

# Retrieve streamed waveform data until desired time has elapsed
measurements_list = []
while (monotonic() - start_time) < acquisition_duration_in_seconds:
measurements_list.append(Measurement(waveforms=card.get_waveforms(), timestamp=card.get_timestamp()))
if measurements_list[-1].timestamp is not None:
print(
f"Got measurement triggered at {measurements_list[-1].timestamp.time()} (acquisition latency of"
f" {(datetime.datetime.now() - measurements_list[-1].timestamp).microseconds * 1e-3} ms)"
)

# Stop the acquisition (and streaming)
card.stop()

card.reset()
card.disconnect()
try:
# Apply settings
card.configure_trigger(trigger_settings)
card.configure_acquisition(acquisition_settings)

# Execute acquisition
start_time = monotonic()
card.execute_continuous_fifo_acquisition()

# Retrieve streamed waveform data until desired time has elapsed
measurements_list = []
while (monotonic() - start_time) < acquisition_duration_in_seconds:

measurements_list += [
Measurement(waveforms=frame, timestamp=card.get_timestamp()) for frame in card.get_waveforms()
]

if measurements_list[-1].timestamp is not None:
print(
f"Got measurement triggered at {measurements_list[-1].timestamp.time()} (acquisition latency of"
f" {(datetime.datetime.now() - measurements_list[-1].timestamp).microseconds * 1e-3} ms)"
)
finally:
# Stop the acquisition (and streaming)
card.stop()

card.reset()
card.disconnect()
return measurements_list


Expand All @@ -88,10 +94,11 @@ def continuous_multi_fifo_example(
from matplotlib.pyplot import plot, show, figure, title, xlabel, ylabel, tight_layout

measurements = continuous_multi_fifo_example(
mock_mode=True,
acquisition_duration_in_seconds=1.0,
mock_mode=False,
acquisition_duration_in_seconds=0.5,
trigger_source=TriggerSource.SPC_TMASK_EXT0,
device_number=0,
device_number=1,
ip_address="169.254.45.181",
)

# Plot waveforms
Expand All @@ -104,7 +111,4 @@ def continuous_multi_fifo_example(
ylabel("Amplitude (Volts)")
tight_layout()

print(f"Completed {len(measurements)} measurements each containing {len(measurements[0].waveforms)} waveforms.")
print(f"Waveforms had the following shape: {measurements[0].waveforms[0].shape}")

show()
Loading

0 comments on commit a9c1d3a

Please sign in to comment.