Source code for pslab.instrument.digital
"""Objects related to the PSLab's digital input channels."""
import numpy as np
DIGITAL_INPUTS = ("LA1", "LA2", "LA3", "LA4", "RES", "EXT", "FRQ")
DIGITAL_OUTPUTS = ("SQ1", "SQ2", "SQ3", "SQ4")
MODES = {
"sixteen rising": 5,
"four rising": 4,
"rising": 3,
"falling": 2,
"any": 1,
"disabled": 0,
}
[docs]class DigitalInput:
"""Model of the PSLab's digital inputs.
Parameters
----------
name : {"LA1", "LA2", "LA3", "LA4", "RES", "FRQ"}
Name of the digital channel to model.
Attributes
----------
name : str
One of {"LA1", "LA2", "LA3", "LA4", "RES", "FRQ"}.
number : int
Number used to refer to this channel in the firmware.
datatype : str
Either "int" or "long", depending on if a 16 or 32-bit counter is used
to capture timestamps for this channel.
events_in_buffer : int
Number of logic events detected on this channel, the timestamps of
which are currently being held in the device's ADC buffer.
buffer_idx : Union[int, None]
Location in the device's ADC buffer where the events are stored. None
if no events captured by this channel are currently held in the buffer.
"""
def __init__(self, name: str):
self.name = name
self.number = DIGITAL_INPUTS.index(self.name)
self.datatype = "long"
self.events_in_buffer = 0
self.buffer_idx = None
self._logic_mode = MODES["any"]
@property
def logic_mode(self) -> str:
"""str: Type of logic event which should be captured on this channel.
The options are:
any: Capture every edge.
rising: Capture every rising edge.
falling: Capture every falling edge.
four rising: Capture every fourth rising edge.
sixteen rising: Capture every fourth rising edge.
"""
return {v: k for k, v in MODES.items()}[self._logic_mode]
def _get_xy(self, initial_state: bool, timestamps: np.ndarray):
x = np.repeat(timestamps, 3)
x = np.insert(x, 0, 0)
x[0] = 0
y = np.array(len(x) * [False])
if self.logic_mode == "any":
y[0] = initial_state
for i in range(1, len(x), 3):
y[i] = y[i - 1] # Value before this timestamp.
y[i + 1] = not y[i] # Value at this timestamp.
y[i + 2] = y[i + 1] # Value leaving this timetamp.
elif self.logic_mode == "falling":
y[0] = True
for i in range(1, len(x), 3):
y[i] = True # Value before this timestamp.
y[i + 1] = False # Value at this timestamp.
y[i + 2] = True # Value leaving this timetamp.
else:
y[0] = False
for i in range(1, len(x), 3):
y[i] = False # Value before this timestamp.
y[i + 1] = True # Value at this timestamp.
y[i + 2] = False # Value leaving this timetamp.
return x, y
[docs]class DigitalOutput:
"""Model of the PSLab's digital outputs.
Parameters
----------
name : {'SQ1', 'SQ2', 'SQ3', 'SQ4'}
Name of the digital output pin represented by the instance.
"""
def __init__(self, name: str):
self._name = name
self._state = "LOW"
self._duty_cycle = 0
self.phase = 0
self.remapped = False
@property
def name(self) -> str:
"""str: Name of this pin."""
return self._name
@name.setter
def name(self, value):
if value in DIGITAL_OUTPUTS:
self._name = value
else:
e = f"Invalid digital output {value}. Choose one of {DIGITAL_OUTPUTS}."
raise ValueError(e)
@property
def state(self) -> str:
"""str: State of the digital output. Can be 'HIGH', 'LOW', or 'PWM'."""
return self._state
@property
def duty_cycle(self) -> float:
"""float: Duty cycle of the PWM signal on this pin."""
return self._duty_cycle
@duty_cycle.setter
def duty_cycle(self, value: float):
if value == 0:
self._state = "LOW"
elif value < 1:
self._state = "PWM"
elif value == 1:
self._state = "HIGH"
else:
raise ValueError("Duty cycle must be in range [0, 1].")
self._duty_cycle = value
@property
def state_mask(self) -> int:
"""int: State mask for this pin.
The state mask is used in the DOUT->SET_STATE command to set the
digital output pins HIGH or LOW. For example:
0x10 | 1 << 0 | 0x40 | 0 << 2 | 0x80 | 1 << 3
would set SQ1 and SQ4 HIGH, SQ3 LOW, and leave SQ2 unchanged.
"""
if self.name == "SQ1":
return 0x10
elif self.name == "SQ2":
return 0x20
elif self.name == "SQ3":
return 0x40
elif self.name == "SQ4":
return 0x80
@property
def reference_clock_map(self) -> int:
"""int: Reference clock map value for this pin.
The reference clock map is used in the WAVEGEN->MAP_REFERENCE command
to map a digital output pin directly to the interal oscillator. This
can be used to achieve very high frequencies, with the caveat that
the only frequencies available are quotients of 128 MHz and powers of
2 up to 15. For example, sending (2 | 4) followed by 3 outputs
128 / (1 << 3) = 16 MHz on SQ2 and SQ3.
"""
if self.name == "SQ1":
return 1
elif self.name == "SQ2":
return 2
elif self.name == "SQ3":
return 4
elif self.name == "SQ4":
return 8