Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement a notify size for faster acquisition, enabling bulk acquisitions #31

Merged
merged 24 commits into from
Oct 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
912b1d4
#26 - AWG interface and implementation underway
crnbaker Jun 5, 2023
b5f36a7
#26 - AWG channel settings implemented
crnbaker Jun 8, 2023
9c38514
#26 - Generation mode Enum and properties implemented
crnbaker Jun 8, 2023
9e02448
#26 - refactored TransferBuffer to use factories rather than inheritence
crnbaker Jun 19, 2023
ae4a20f
#26 - started implementing SpectrumAWGCard
crnbaker Jun 19, 2023
ba82cf6
#26 - linting
crnbaker Jul 5, 2023
51e788b
#26 - abstract spectrum awg
crnbaker Jul 5, 2023
260b894
#26 - Added option to set a notify size smaller than the data length …
crnbaker Jul 5, 2023
2a6136d
#30 - made star hub acquisition multithreaded
crnbaker Jul 6, 2023
096f59e
#30 - Added option to set a notify size smaller than the data length …
crnbaker Jul 5, 2023
a3e8e1f
#30 - WIP modified get_waveforms() to acquire batches of acquisitions
crnbaker Jul 6, 2023
bc28c22
#30 - linting
crnbaker Jul 6, 2023
05410f5
#30 - batch acquisition working with multithreaded star hub
crnbaker Jul 6, 2023
7fa27bc
#30 - fixed standard single mode
crnbaker Jul 6, 2023
d3bfaef
#30 - removed prints
crnbaker Jul 6, 2023
0a425e5
#30 - removed AWG code so this branch can be merged to main and a rel…
crnbaker Jul 18, 2023
c8d43b0
#30 - removed AWG TODOs
crnbaker Jul 18, 2023
9d6a0ad
#30 - made define_transfer_buffer only do anything if a buffer is pro…
crnbaker Jul 18, 2023
b370c5d
#30 - updated readme to explain notify size
crnbaker Jul 18, 2023
024b89c
#30 - updated mock digitisers to use notify size
crnbaker Jul 20, 2023
1332836
#30 - linting
crnbaker Jul 20, 2023
6c28fd1
#30 - mock digitiser card now overloads define_transfer_buffer with v…
crnbaker Jul 21, 2023
6597014
#30 - debugged FIFO mode on hardware
crnbaker Jul 25, 2023
957a6fd
#30 - linting
crnbaker Oct 3, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading