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

Pulse generator function #45

Merged
merged 12 commits into from
Jan 17, 2024
9 changes: 3 additions & 6 deletions src/example_scripts/awg_example.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from time import sleep

from matplotlib.pyplot import plot, show
from numpy import int16, iinfo, linspace, sin, pi
from numpy import int16

from spectrumdevice.devices.awg.awg_card import SpectrumAWGCard
from spectrumdevice.devices.awg.synthesis import make_full_scale_sine_waveform
from spectrumdevice.settings import TriggerSettings, TriggerSource, ExternalTriggerMode
from spectrumdevice.settings.channel import OutputChannelStopLevelMode
from spectrumdevice.settings.device_modes import GenerationMode
Expand All @@ -27,12 +28,8 @@
)
card.configure_trigger(trigger_settings)

full_scale_min_value = iinfo(int16).min
full_scale_max_value = iinfo(int16).max
t, analog_wfm = make_full_scale_sine_waveform(FREQUENCY, SAMPLE_RATE, NUM_CYCLES, dtype=int16)

duration = NUM_CYCLES / FREQUENCY
t = linspace(0, duration, int(duration * SAMPLE_RATE + 1))
analog_wfm = (sin(2 * pi * FREQUENCY * t) * full_scale_max_value).astype(int16)
card.set_sample_rate_in_hz(SAMPLE_RATE)
card.set_generation_mode(GenerationMode.SPC_REP_STD_SINGLERESTART)
card.set_num_loops(NUM_PULSES)
Expand Down
68 changes: 68 additions & 0 deletions src/example_scripts/trigger_awg_with_pulse_generator_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
from time import sleep

from numpy import int16

from spectrumdevice.devices.awg.awg_card import SpectrumAWGCard
from spectrumdevice.devices.awg.synthesis import make_full_scale_sine_waveform
from spectrumdevice.settings import TriggerSettings, TriggerSource, ExternalTriggerMode, IOLineMode
from spectrumdevice.settings.channel import OutputChannelStopLevelMode
from spectrumdevice.settings.device_modes import GenerationMode
from spectrumdevice.settings.pulse_generator import (
PulseGeneratorOutputSettings,
PulseGeneratorTriggerSettings,
PulseGeneratorTriggerMode,
PulseGeneratorTriggerDetectionMode,
PulseGeneratorMultiplexer1TriggerSource,
PulseGeneratorMultiplexer2TriggerSource,
)


SAMPLE_RATE = 40000000


if __name__ == "__main__":

card = SpectrumAWGCard(device_number=0)

card_trigger_settings = TriggerSettings(
trigger_sources=[TriggerSource.SPC_TMASK_EXT2], # ext1 trigger source is first IOLine
external_trigger_mode=ExternalTriggerMode.SPC_TM_POS,
)

t, analog_wfm = make_full_scale_sine_waveform(
frequency_in_hz=1e6, sample_rate_hz=SAMPLE_RATE, num_cycles=1, dtype=int16
)

# Set up AWG card
card.configure_trigger(card_trigger_settings)
card.set_sample_rate_in_hz(SAMPLE_RATE)
card.set_generation_mode(GenerationMode.SPC_REP_STD_SINGLERESTART)
card.set_num_loops(1)
card.transfer_waveform(analog_wfm)
card.analog_channels[0].set_stop_level_mode(OutputChannelStopLevelMode.SPCM_STOPLVL_ZERO)
card.analog_channels[0].set_is_switched_on(True)
card.analog_channels[0].set_signal_amplitude_in_mv(1000)

pulse_trigger_settings = PulseGeneratorTriggerSettings(
trigger_mode=PulseGeneratorTriggerMode.SPCM_PULSEGEN_MODE_SINGLESHOT,
trigger_detection_mode=PulseGeneratorTriggerDetectionMode.RISING_EDGE,
multiplexer_1_source=PulseGeneratorMultiplexer1TriggerSource.SPCM_PULSEGEN_MUX1_SRC_UNUSED,
multiplexer_1_output_inversion=False,
multiplexer_2_source=PulseGeneratorMultiplexer2TriggerSource.SPCM_PULSEGEN_MUX2_SRC_SOFTWARE,
multiplexer_2_output_inversion=False,
)

pulse_output_settings = PulseGeneratorOutputSettings(
period_in_seconds=1e-3, duty_cycle=0.5, num_pulses=10, delay_in_seconds=0.0, output_inversion=False
)

card.io_lines[0].set_mode(IOLineMode.SPCM_XMODE_PULSEGEN)
card.io_lines[0].pulse_generator.configure_trigger(pulse_trigger_settings)
card.io_lines[0].pulse_generator.configure_output(pulse_output_settings)

card.start()

card.io_lines[0].pulse_generator.force_trigger()
sleep(1)
card.stop()
card.disconnect()
4 changes: 0 additions & 4 deletions src/spectrumdevice/devices/abstract_device/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,9 @@
from spectrumdevice.devices.abstract_device.abstract_spectrum_channel import AbstractSpectrumChannel
from spectrumdevice.devices.abstract_device.abstract_spectrum_device import AbstractSpectrumDevice
from spectrumdevice.devices.abstract_device.abstract_spectrum_hub import AbstractSpectrumStarHub
from spectrumdevice.devices.abstract_device.interfaces import SpectrumChannelInterface, SpectrumDeviceInterface


__all__ = [
"SpectrumChannelInterface",
"AbstractSpectrumChannel",
"SpectrumDeviceInterface",
"AbstractSpectrumDevice",
"AbstractSpectrumCard",
"AbstractSpectrumStarHub",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from abc import ABC, abstractmethod
from functools import reduce
from operator import or_
from typing import Any, List, Optional, Sequence, Tuple, TypeVar, Generic
from typing import Any, List, Optional, Sequence, Tuple

from spectrum_gmbh.regs import (
M2CMD_DATA_STARTDMA,
Expand All @@ -35,7 +35,7 @@
SPC_MIINST_BYTESPERSAMPLE,
)
from spectrumdevice.devices.abstract_device.abstract_spectrum_device import AbstractSpectrumDevice
from spectrumdevice.devices.abstract_device.interfaces import SpectrumAnalogChannelInterface, SpectrumIOLineInterface
from spectrumdevice.devices.abstract_device.device_interface import AnalogChannelInterfaceType, IOLineInterfaceType
from spectrumdevice.exceptions import (
SpectrumExternalTriggerNotEnabled,
SpectrumInvalidNumberOfEnabledChannels,
Expand Down Expand Up @@ -71,11 +71,9 @@

# Use a Generic and Type Variables to allow subclasses of AbstractSpectrumCard to define whether they own AWG analog
# channels or Digitiser analog channels and IO lines
AnalogChannelInterfaceType = TypeVar("AnalogChannelInterfaceType", bound=SpectrumAnalogChannelInterface)
IOLineInterfaceType = TypeVar("IOLineInterfaceType", bound=SpectrumIOLineInterface)


class AbstractSpectrumCard(AbstractSpectrumDevice, Generic[AnalogChannelInterfaceType, IOLineInterfaceType], ABC):
class AbstractSpectrumCard(AbstractSpectrumDevice[AnalogChannelInterfaceType, IOLineInterfaceType], ABC):
"""Abstract superclass implementing methods common to all individual "card" devices (as opposed to "hub" devices)."""

def __init__(self, device_number: int, ip_address: Optional[str] = None, **kwargs: Any):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@
# Copyright (c) 2021 School of Biomedical Engineering & Imaging Sciences, King's College London
# Licensed under the MIT. You may obtain a copy at https://opensource.org/licenses/MIT.

from spectrumdevice.devices.abstract_device.interfaces import (
SpectrumDeviceInterface,
from spectrumdevice.devices.abstract_device.channel_interfaces import (
SpectrumChannelInterface,
SpectrumAnalogChannelInterface,
)
from spectrumdevice.devices.abstract_device.device_interface import SpectrumDeviceInterface
from spectrumdevice.settings import SpectrumRegisterLength
from spectrumdevice.settings.channel import SpectrumAnalogChannelName, SpectrumChannelName


Expand Down Expand Up @@ -48,6 +49,21 @@ def name(self) -> ChannelNameType:
def _number(self) -> int:
return int(self.name.name.split(self._name_prefix)[-1])

def write_to_parent_device_register(
self,
spectrum_register: int,
value: int,
length: SpectrumRegisterLength = SpectrumRegisterLength.THIRTY_TWO,
) -> None:
self._parent_device.write_to_spectrum_device_register(spectrum_register, value, length)

def read_parent_device_register(
self,
spectrum_register: int,
length: SpectrumRegisterLength = SpectrumRegisterLength.THIRTY_TWO,
) -> int:
return self._parent_device.read_spectrum_device_register(spectrum_register, length)

def __eq__(self, other: object) -> bool:
if isinstance(other, AbstractSpectrumChannel):
return (self.name == other.name) and (self._parent_device == other._parent_device)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@

from abc import ABC

from spectrumdevice.devices.abstract_device.interfaces import SpectrumDeviceInterface
from spectrumdevice.devices.abstract_device.device_interface import (
SpectrumDeviceInterface,
AnalogChannelInterfaceType,
IOLineInterfaceType,
)
from spectrumdevice.exceptions import SpectrumDeviceNotConnected, SpectrumDriversNotFound
from spectrumdevice.settings import SpectrumRegisterLength, TriggerSettings
from spectrumdevice.settings.triggering import EXTERNAL_TRIGGER_SOURCES
Expand All @@ -28,7 +32,7 @@
)


class AbstractSpectrumDevice(SpectrumDeviceInterface, ABC):
class AbstractSpectrumDevice(SpectrumDeviceInterface[AnalogChannelInterfaceType, IOLineInterfaceType], ABC):
"""Abstract superclass which implements methods common to all Spectrum devices. Instances of this class
cannot be constructed directly. Instead, construct instances of the concrete classes listed in
spectrumdevice/__init__.py, which inherit the methods defined here. Note that the concrete mock devices override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@

from spectrum_gmbh.regs import SPC_SYNC_ENABLEMASK
from spectrumdevice.devices.abstract_device.abstract_spectrum_device import AbstractSpectrumDevice
from spectrumdevice.devices.abstract_device.interfaces import (
SpectrumDeviceInterface,
from spectrumdevice.devices.abstract_device.channel_interfaces import (
SpectrumAnalogChannelInterface,
SpectrumIOLineInterface,
)
from spectrumdevice.devices.abstract_device.device_interface import SpectrumDeviceInterface
from spectrumdevice.exceptions import SpectrumSettingsMismatchError
from spectrumdevice.settings import (
AdvancedCardFeature,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
from abc import ABC, abstractmethod
from typing import Any, Optional

from spectrumdevice.devices.abstract_device import AbstractSpectrumChannel
from spectrumdevice.devices.abstract_device.interfaces import SpectrumIOLineInterface
from spectrumdevice.devices.abstract_device.channel_interfaces import SpectrumIOLineInterface
from spectrumdevice.exceptions import SpectrumFeatureNotSupportedByCard
from spectrumdevice.features.pulse_generator.pulse_generator import PulseGenerator
from spectrumdevice.features.pulse_generator.interfaces import PulseGeneratorInterface
from spectrumdevice.settings import IOLineMode
from spectrumdevice.settings.io_lines import IO_LINE_MODE_COMMANDS, SpectrumIOLineName
from spectrumdevice.settings.io_lines import IO_LINE_MODE_COMMANDS, SpectrumIOLineName, decode_enabled_io_line_mode


class AbstractSpectrumIOLine(SpectrumIOLineInterface, AbstractSpectrumChannel[SpectrumIOLineName], ABC):
"""Partially implemented abstract superclass contain code common for controlling an individual IO Line of all
spectrum devices."""

def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs)
try:
self._pulse_generator: Optional[PulseGenerator] = PulseGenerator(parent=self)
except SpectrumFeatureNotSupportedByCard:
self._pulse_generator = None

@property
def _name_prefix(self) -> str:
return "X"
Expand All @@ -23,9 +34,21 @@ def _get_io_line_mode_settings_mask(self, mode: IOLineMode) -> int:

@property
def mode(self) -> IOLineMode:
# todo: this may contain dig out settings bits, so needs a decode function that ignores those bits
return IOLineMode(self._parent_device.read_spectrum_device_register(IO_LINE_MODE_COMMANDS[self._number]))
return decode_enabled_io_line_mode(
self._parent_device.read_spectrum_device_register(IO_LINE_MODE_COMMANDS[self._number])
)

def set_mode(self, mode: IOLineMode) -> None:
value_to_write = self._get_io_line_mode_settings_mask(mode) | mode.value
self._parent_device.write_to_spectrum_device_register(IO_LINE_MODE_COMMANDS[self._number], value_to_write)

@property
def pulse_generator(self) -> PulseGeneratorInterface:
"""Gets the IO line's pulse generator."""
if self._pulse_generator is not None:
return self._pulse_generator
else:
raise SpectrumFeatureNotSupportedByCard(
call_description=self.__str__() + ".pulse_generator()",
message="Pulse generator firmware option not enabled.",
)
74 changes: 74 additions & 0 deletions src/spectrumdevice/devices/abstract_device/channel_interfaces.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"""Defines a common public interface for controlling all Spectrum devices and their channels."""

# Christian Baker, King's College London
# Copyright (c) 2021 School of Biomedical Engineering & Imaging Sciences, King's College London
# Licensed under the MIT. You may obtain a copy at https://opensource.org/licenses/MIT.

from abc import ABC, abstractmethod
from typing import TypeVar, Generic

from spectrumdevice.features.pulse_generator.interfaces import PulseGeneratorInterface
from spectrumdevice.settings import (
SpectrumRegisterLength,
IOLineMode,
)
from spectrumdevice.settings.channel import SpectrumAnalogChannelName, SpectrumChannelName
from spectrumdevice.settings.io_lines import SpectrumIOLineName

ChannelNameType = TypeVar("ChannelNameType", bound=SpectrumChannelName)


class SpectrumChannelInterface(Generic[ChannelNameType], ABC):
"""Defines the common public interface for control of the channels of Digitiser and AWG devices including
Multipurpose IO Lines. All properties are read-only and must be set with their respective setter methods."""

@property
@abstractmethod
def name(self) -> ChannelNameType:
raise NotImplementedError

@abstractmethod
def write_to_parent_device_register(
self,
spectrum_register: int,
value: int,
length: SpectrumRegisterLength = SpectrumRegisterLength.THIRTY_TWO,
) -> None:
raise NotImplementedError()

@abstractmethod
def read_parent_device_register(
self,
spectrum_register: int,
length: SpectrumRegisterLength = SpectrumRegisterLength.THIRTY_TWO,
) -> int:
raise NotImplementedError()


class SpectrumAnalogChannelInterface(SpectrumChannelInterface[SpectrumAnalogChannelName], ABC):
"""Defines the common public interface for control of the analog channels of Digitiser and AWG devices. All
properties are read-only and must be set with their respective setter methods."""

pass


class SpectrumIOLineInterface(SpectrumChannelInterface[SpectrumIOLineName], ABC):
"""Defines the common public interface for control of the Multipurpose IO Lines of Digitiser and AWG devices. All
properties are read-only and must be set with their respective setter methods."""

@property
@abstractmethod
def mode(self) -> IOLineMode:
"""Returns the current mode of the IO Line."""
raise NotImplementedError()

@abstractmethod
def set_mode(self, mode: IOLineMode) -> None:
"""Sets the current mode of the IO Line"""
raise NotImplementedError()

@property
@abstractmethod
def pulse_generator(self) -> PulseGeneratorInterface:
"""Gets the IO line's pulse generator."""
raise NotImplementedError()
Loading
Loading