Skip to content

Commit

Permalink
Merge pull request #45 from KCL-BMEIS/42_pulse_gen
Browse files Browse the repository at this point in the history
Pulse generator function
  • Loading branch information
crnbaker authored Jan 17, 2024
2 parents c5fdd16 + cf1e70c commit d82be36
Show file tree
Hide file tree
Showing 32 changed files with 1,651 additions and 98 deletions.
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

0 comments on commit d82be36

Please sign in to comment.