Skip to content

Commit

Permalink
Merge pull request #49 from KCL-BMEIS/48_hardware_awg_example_tests
Browse files Browse the repository at this point in the history
Fixed minor bugs in AWG code found during HW tests
  • Loading branch information
crnbaker authored Jan 24, 2024
2 parents 8037b9f + f5fb644 commit 3b912a4
Show file tree
Hide file tree
Showing 25 changed files with 101 additions and 75 deletions.
30 changes: 17 additions & 13 deletions src/example_scripts/awg_standard_single_restart_mode_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@
from spectrumdevice.settings import (
TriggerSettings,
TriggerSource,
ExternalTriggerMode,
GenerationSettings,
OutputChannelFilter,
ModelNumber,
)
from spectrumdevice.settings.channel import OutputChannelStopLevelMode
from spectrumdevice.settings.device_modes import GenerationMode

PULSE_RATE_HZ = 5000
NUM_PULSES = 5
NUM_CYCLES = 2
FREQUENCY = 20e3
PULSE_RATE_HZ = 200
NUM_TRANSMISSIONS = 5
NUM_CYCLES_PER_TRANSMISSION = 3
FREQUENCY_HZ = 1e3
AMPLITUDE_V = 1.0
SAMPLE_RATE = 125000000


Expand All @@ -33,12 +33,15 @@ def awg_single_restart_mode_example(mock_mode: bool) -> None:
device_number=0, model=ModelNumber.TYP_M2P6560_X4, num_modules=1, num_channels_per_module=1
)

sample_rate_in_hz = 1000000
number_of_generations = 3
sample_rate_in_hz = SAMPLE_RATE
number_of_generations = NUM_TRANSMISSIONS

# create a waveform to generate
t, analog_wfm = make_full_scale_sine_waveform(
frequency_in_hz=20e3, sample_rate_in_hz=sample_rate_in_hz, num_cycles=1, dtype=int16
frequency_in_hz=FREQUENCY_HZ,
sample_rate_in_hz=sample_rate_in_hz,
num_cycles=NUM_CYCLES_PER_TRANSMISSION,
dtype=int16,
)

# configure signal generation
Expand All @@ -48,28 +51,29 @@ def awg_single_restart_mode_example(mock_mode: bool) -> None:
sample_rate_in_hz=sample_rate_in_hz,
num_loops=number_of_generations,
enabled_channels=[0],
signal_amplitudes_in_mv=[1000],
signal_amplitudes_in_mv=[int(round((AMPLITUDE_V * 1000)))],
dc_offsets_in_mv=[0],
output_filters=[OutputChannelFilter.LOW_PASS_70_MHZ],
stop_level_modes=[OutputChannelStopLevelMode.SPCM_STOPLVL_ZERO],
)
card.configure_generation(generation_settings)

# configure triggering (here we set to software trigger)
# configure triggering (here we configure an external trigger, but actually we will be forcing
# the trigger event to occur from software on line 72)
trigger_settings = TriggerSettings(
trigger_sources=[TriggerSource.SPC_TMASK_EXT0],
external_trigger_mode=ExternalTriggerMode.SPC_TM_POS,
external_trigger_level_in_mv=200,
)
card.configure_trigger(trigger_settings)

# start the card and then force a trigger for each generation we want to perform
# we are using GenerationMode.SPC_REP_STD_SINGLERESTART so the whole waveform will be generated each time the card
# is trigger, until "num_loops" triggers have been detected.
card.start()

# force triggers at the requested rate. Remove if real external trigger present.
for _ in range(number_of_generations):
card.force_trigger()
sleep(100e-3) # here we are waiting 0.1 seconds between triggers
sleep(1 / PULSE_RATE_HZ)
print("generated pulse")
card.stop()
card.disconnect()
Expand Down
46 changes: 25 additions & 21 deletions src/example_scripts/awg_trigger_with_pulse_generator_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from numpy import int16

from spectrumdevice import SpectrumAWGCard
from spectrumdevice.devices.awg.synthesis import make_full_scale_rect_waveform
from spectrumdevice.devices.mocks import MockSpectrumAWGCard
from spectrumdevice.settings import (
Expand All @@ -25,27 +26,28 @@
PulseGeneratorMultiplexer2TriggerSource,
)


SAMPLE_RATE_IN_HZ = 40000000
NUM_GENERATIONS = 4
MOCK_CARD = True
SAMPLE_RATE_IN_HZ = 1000000
NUM_GENERATIONS = 3


if __name__ == "__main__":

# AWG CARD SETUP ---------------------------------------------------------------------------------------------------

# Connect to a real AWG card with the optional pulse generator firmware option unlocked
# card = SpectrumAWGCard(device_number=0)

# Or a mock AWG card
card = MockSpectrumAWGCard(
device_number=0,
model=ModelNumber.TYP_M2P6560_X4,
num_modules=1,
num_channels_per_module=1,
# make sure the mock card has the pulse generator feature unlocked!
advanced_card_features=[AdvancedCardFeature.SPCM_FEAT_EXTFW_PULSEGEN],
)
if not MOCK_CARD:
# Connect to a real AWG card with the optional pulse generator firmware option unlocked
card = SpectrumAWGCard(device_number=0)
else:
# Or a mock AWG card
card = MockSpectrumAWGCard(
device_number=0,
model=ModelNumber.TYP_M2P6560_X4,
num_modules=1,
num_channels_per_module=1,
# make sure the mock card has the pulse generator feature unlocked!
advanced_card_features=[AdvancedCardFeature.SPCM_FEAT_EXTFW_PULSEGEN],
)

# Set up the AWG trigger settings using the X1 multipurpose IO Line as a trigger source
card_trigger_settings = TriggerSettings(
Expand Down Expand Up @@ -107,27 +109,29 @@
# continuous output
# The period is the length of the whole pulse (high-voltage length + 0V length)
# The duty cycle is the high-voltage length divided by the period

print(f"SAMPLE RATE IS {card.sample_rate_in_hz} Hz")

pulse_output_settings = PulseGeneratorOutputSettings(
period_in_seconds=1e-3, duty_cycle=0.5, num_pulses=NUM_GENERATIONS, delay_in_seconds=0.0, output_inversion=False
period_in_seconds=1e-3, duty_cycle=0.1, num_pulses=NUM_GENERATIONS, delay_in_seconds=0.0, output_inversion=False
)
pulse_gen.configure_output(pulse_output_settings)
pulse_gen.configure_output(pulse_output_settings, coerce=False)

# Enable the pulse generator
pulse_gen.enable()

# Start the AWG so it is waiting for a trigger
card.start()
sleep(0.2)

# Force a software trigger on the pulse generator, causing pulse to be generated on X1, triggering the AWG
pulse_gen.force_trigger()
# Wait for the pulse sequence to complete
sleep(1)

# Note that there is a delay between a trigger being received and the AWG generating a signal.
# This is in the technical data section of the manual, and for my device is apparently 63 samples + 7 ns
# However I see 74 samples + 7 ns when testing this script, suggesting there is an additional delay of 11 samples
# between the pulse generator and the AWG trigger circuitry
print(f"Expected delay between pulse and signal: {(63 * 1 / SAMPLE_RATE_IN_HZ + 7e-9) * 1e6} microseconds")
# This is in the technical data section of the manual, and for my device is apparently 73 samples + 7 ns
print(f"Expected delay between pulse and signal: {(73 * 1 / SAMPLE_RATE_IN_HZ + 7e-9) * 1e6} microseconds")

card.stop()
card.disconnect()
22 changes: 17 additions & 5 deletions src/example_scripts/pulse_generator_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,16 @@ def pulse_generator_example(mock_mode: bool) -> None:
advanced_card_features=[AdvancedCardFeature.SPCM_FEAT_EXTFW_PULSEGEN],
)

# Set the card's sample rate. This affects the precision with which pulse timings can be chosen, and the min and max
# allowed pulse periods
card.set_sample_rate_in_hz(100000)
# Enable a single channel of the card. Although not used in this example, the number of enabled channels affects
# the precision with which pulse timings can be chosen and the min and max allowed pulse periods
card.set_enabled_analog_channels([0])

# Each of the card's four multipurpose IO lines (X0, X1, X2 and X3) has a pulse generator
# Choose the one you would like to use and set it to pulse gen mode. Here we are using X0 (index 0)
io_line_index = 0
# Choose the one you would like to use and set it to pulse gen mode. Here we are using X1 (index 1)
io_line_index = 1
card.io_lines[io_line_index].set_mode(IOLineMode.SPCM_XMODE_PULSEGEN)
# Then get its pulse generator
pulse_gen = card.io_lines[io_line_index].pulse_generator
Expand All @@ -55,9 +62,14 @@ def pulse_generator_example(mock_mode: bool) -> None:
# The period is the length of the whole pulse (high-voltage length + 0V length)
# The duty cycle is the high-voltage length divided by the period
pulse_output_settings = PulseGeneratorOutputSettings(
period_in_seconds=1e-3, duty_cycle=0.5, num_pulses=10, delay_in_seconds=0.0, output_inversion=False
period_in_seconds=1e-3, duty_cycle=0.5, num_pulses=2, delay_in_seconds=0.0, output_inversion=False
)
pulse_gen.configure_output(pulse_output_settings)
pulse_gen.configure_output(pulse_output_settings, coerce=False)

# Print pulse timings. They may have been coerced to allowed values
print(f"Pulse period: {pulse_gen.period_in_seconds * 1e3} ms")
print(f"Duty cycle: {pulse_gen.duty_cycle}")

# Enable the pulse generator
pulse_gen.enable()

Expand All @@ -69,4 +81,4 @@ def pulse_generator_example(mock_mode: bool) -> None:


if __name__ == "__main__":
pulse_generator_example(mock_mode=True)
pulse_generator_example(mock_mode=False)
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from operator import or_
from typing import Any, List, Optional, Sequence, Tuple

from spectrum_gmbh.regs import (
from spectrum_gmbh.py_header.regs import (
M2CMD_DATA_STARTDMA,
M2CMD_DATA_STOPDMA,
M2CMD_DATA_WAITDMA,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
DOUBLING_CHANNEL_PAIR_COMMANDS,
)
from spectrumdevice.settings.triggering import EXTERNAL_TRIGGER_SOURCES
from spectrum_gmbh.regs import (
from spectrum_gmbh.py_header.regs import (
M2CMD_CARD_ENABLETRIGGER,
M2CMD_CARD_RESET,
M2CMD_CARD_START,
Expand Down Expand Up @@ -185,13 +185,13 @@ def read_spectrum_device_register(
Args:
spectrum_register (int): Identifier of the register to set. This should be a global constant imported from
spectrum_gmbh.regs.
spectrum_gmbh.py_header.regs.
length (`SpectrumRegisterLength`): A `SpectrumRegisterLength` object specifying the length of the register
to set, in bits.
Returns:
value (int): Value of the register. This can be matched to a global constant imported from
spectrum_gmbh.regs, usually using one of the Enums defined in the settings module.
spectrum_gmbh.py_header.regs, usually using one of the Enums defined in the settings module.
"""
if not SPECTRUM_DRIVERS_FOUND:
raise SpectrumDriversNotFound(
Expand Down
4 changes: 2 additions & 2 deletions src/spectrumdevice/devices/awg/awg_channel.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Any, cast
from typing import Any

from numpy import int16

Expand Down Expand Up @@ -49,7 +49,7 @@ def set_dig_out_settings(self, dig_out_settings: DigOutIOLineModeSettings) -> No

def _get_io_line_mode_settings_mask(self, mode: IOLineMode) -> int:
if mode == IOLineMode.SPCM_XMODE_DIGOUT:
return cast(int, self._dig_out_settings.source_channel.value | self._dig_out_settings.source_bit.value)
return self._dig_out_settings.source_channel.value | self._dig_out_settings.source_bit.value
else:
return 0

Expand Down
2 changes: 1 addition & 1 deletion src/spectrumdevice/devices/digitiser/digitiser_card.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from numpy import float_, mod, squeeze, zeros
from numpy.typing import NDArray

from spectrum_gmbh.regs import (
from spectrum_gmbh.py_header.regs import (
M2CMD_CARD_WAITREADY,
SPC_AVERAGES,
SPC_CARDMODE,
Expand Down
2 changes: 1 addition & 1 deletion src/spectrumdevice/devices/digitiser/digitiser_channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from numpy import ndarray

from spectrum_gmbh.regs import SPC_MIINST_MAXADCVALUE
from spectrum_gmbh.py_header.regs import SPC_MIINST_MAXADCVALUE
from spectrumdevice.devices.abstract_device import AbstractSpectrumCard
from spectrumdevice.devices.abstract_device.abstract_spectrum_channel import AbstractSpectrumAnalogChannel
from spectrumdevice.devices.abstract_device.abstract_spectrum_io_line import AbstractSpectrumIOLine
Expand Down
4 changes: 2 additions & 2 deletions src/spectrumdevice/devices/mocks/mock_abstract_devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from threading import Event, Lock, Thread
from typing import Any, Dict, Optional, Union, cast

from spectrum_gmbh.regs import (
from spectrum_gmbh.py_header.regs import (
SPCM_X0_AVAILMODES,
SPCM_X1_AVAILMODES,
SPCM_X2_AVAILMODES,
Expand Down Expand Up @@ -210,7 +210,7 @@ def __init__(
param_dict[SPC_MIINST_BYTESPERSAMPLE] = 2
param_dict[SPC_MIINST_MAXADCVALUE] = 128
# Pulse generation:
param_dict[SPC_XIO_PULSEGEN_CLOCK] = 1000
param_dict[SPC_XIO_PULSEGEN_CLOCK] = 100000
param_dict[SPC_XIO_PULSEGEN_ENABLE] = 0
param_dict[SPC_XIO_PULSEGEN0_CONFIG] = 0
param_dict[SPC_XIO_PULSEGEN1_CONFIG] = 0
Expand Down
2 changes: 1 addition & 1 deletion src/spectrumdevice/devices/mocks/mock_waveform_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from numpy import ndarray
from numpy.random import uniform

from spectrum_gmbh.regs import SPC_DATA_AVAIL_USER_LEN, SPC_DATA_AVAIL_USER_POS
from spectrum_gmbh.py_header.regs import SPC_DATA_AVAIL_USER_LEN, SPC_DATA_AVAIL_USER_POS
from spectrumdevice.settings import AcquisitionMode
from spectrumdevice.settings.transfer_buffer import PAGE_SIZE_IN_BYTES

Expand Down
2 changes: 1 addition & 1 deletion src/spectrumdevice/devices/mocks/timestamps.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from numpy import uint64

from spectrum_gmbh.regs import SPC_TIMESTAMP_CMD
from spectrum_gmbh.py_header.regs import SPC_TIMESTAMP_CMD
from spectrumdevice.devices.spectrum_timestamper import Timestamper
from spectrumdevice.settings.timestamps import TimestampMode
from spectrumdevice.spectrum_wrapper import DEVICE_HANDLE_TYPE
Expand Down
2 changes: 1 addition & 1 deletion src/spectrumdevice/devices/spectrum_timestamper.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from datetime import datetime, timedelta
from typing import Tuple, Optional

from spectrum_gmbh.regs import (
from spectrum_gmbh.py_header.regs import (
SPC_TIMESTAMP_CMD,
SPC_TS_RESET,
SPC_M2CMD,
Expand Down
4 changes: 2 additions & 2 deletions src/spectrumdevice/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,8 @@ def __init__(
self, param_name: str, requested_value: float, param_min: float, param_max: float, param_step: float
) -> None:
super().__init__(
f"The requested {param_name} value of {requested_value} is invalid. It must be between "
f"{param_min} and {param_max} inclusive, and a multiple of {param_step}."
f"The requested {param_name} value of {requested_value} is invalid. At the current sample rate, it must be"
f" between {param_min} and {param_max} inclusive, and a multiple of {param_step}."
)


Expand Down
19 changes: 11 additions & 8 deletions src/spectrumdevice/features/pulse_generator/pulse_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
SPCM_PULSEGEN_CMD_FORCE,
SPC_M2CMD,
M2CMD_CARD_WRITESETUP,
SPC_XIO_PULSEGEN_AVAILHIGH_STEP,
SPC_XIO_PULSEGEN_AVAILHIGH_MAX,
SPC_XIO_PULSEGEN_AVAILHIGH_MIN,
)
from spectrumdevice.devices.abstract_device.channel_interfaces import SpectrumIOLineInterface
from spectrumdevice.exceptions import (
Expand Down Expand Up @@ -198,13 +201,13 @@ def set_trigger_mode(self, mode: PulseGeneratorTriggerMode) -> None:
@property
def min_allowed_period_in_seconds(self) -> float:
reg_val = self.read_parent_device_register(SPC_XIO_PULSEGEN_AVAILLEN_MIN)
reg_val = 0 if reg_val == -1 else reg_val
reg_val = 0 if reg_val < 0 else reg_val
return self._convert_clock_cycles_to_seconds(reg_val)

@property
def max_allowed_period_in_seconds(self) -> float:
reg_val = self.read_parent_device_register(SPC_XIO_PULSEGEN_AVAILLEN_MAX)
reg_val = iinfo(int16).max if reg_val == -1 else reg_val
reg_val = iinfo(int16).max if reg_val < 0 else reg_val
return self._convert_clock_cycles_to_seconds(reg_val)

@property
Expand Down Expand Up @@ -248,23 +251,23 @@ def set_period_in_seconds(self, period: float, coerce: bool = False) -> float:

@property
def min_allowed_high_voltage_duration_in_seconds(self) -> float:
reg_val = self.read_parent_device_register(SPC_XIO_PULSEGEN_AVAILLEN_MIN)
reg_val = 0 if reg_val == -1 else reg_val
reg_val = self.read_parent_device_register(SPC_XIO_PULSEGEN_AVAILHIGH_MIN)
reg_val = 0 if reg_val < 0 else reg_val
return self._convert_clock_cycles_to_seconds(reg_val)

@property
def max_allowed_high_voltage_duration_in_seconds(self) -> float:
reg_val = self.read_parent_device_register(SPC_XIO_PULSEGEN_AVAILLEN_MAX)
reg_val = iinfo(int16).max if reg_val == -1 else reg_val
reg_val = self.read_parent_device_register(SPC_XIO_PULSEGEN_AVAILHIGH_MAX)
reg_val = iinfo(int16).max if reg_val < 0 else reg_val
return self._convert_clock_cycles_to_seconds(reg_val)

@property
def _allowed_high_voltage_duration_step_size_in_clock_cycles(self) -> int:
return self.read_parent_device_register(SPC_XIO_PULSEGEN_AVAILLEN_STEP)
return self.read_parent_device_register(SPC_XIO_PULSEGEN_AVAILHIGH_STEP)

@property
def allowed_high_voltage_duration_step_size_in_seconds(self) -> float:
return self._convert_clock_cycles_to_seconds(self._allowed_period_step_size_in_clock_cycles)
return self._convert_clock_cycles_to_seconds(self._allowed_high_voltage_duration_step_size_in_clock_cycles)

@property
def duration_of_high_voltage_in_seconds(self) -> float:
Expand Down
2 changes: 1 addition & 1 deletion src/spectrumdevice/settings/card_dependent_properties.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from enum import Enum

from spectrum_gmbh.regs import (
from spectrum_gmbh.py_header.regs import (
SPCM_TYPE_AI,
SPCM_TYPE_AO,
SPCM_TYPE_DI,
Expand Down
Loading

0 comments on commit 3b912a4

Please sign in to comment.