# Copyright © PyroLab Project Contributors
# Licensed under the terms of the GNU GPLv3+ License
# (see pyrolab/__init__.py for details)
"""
Analog Discovery
================
Driver for the Digilent Analog Discovery 3.
.. attention::
Requires the `waveform SDK <https://github.com/Digilent/WaveForms-SDK-Getting-Started-PY#egg=WF_SDK>`_
from Digilent, which can be installed with:
.. code-block:: shell
pip install git+https://github.com/Digilent/WaveForms-SDK-Getting-Started-PY#egg=WF_SDK"
.. admonition:: Dependencies
:class: note
WF_SDK (`repository <https://github.com/Digilent/WaveForms-SDK-Getting-Started-PY#egg=WF_SDK>`_)
"""
from WF_SDK import device, scope, wavegen, tools, error
from pyrolab.drivers.fpgas import FPGA
from pyrolab.api import expose, behavior
[docs]
@behavior(instance_mode="single")
@expose
class AnalogDiscovery(FPGA):
"""
A Digilent Analog Discovery 3.
The board has 2 analog inputs, 2 analog outputs, 16 digital inputs, and 16 digital outputs.
At the moment, only the analog inputs and outputs are implemented, and only the scope and wavegen
functionalities are implemented.
"""
[docs]
def autoconnect(self):
"""
Connect to the board.
"""
self._device_data = device.open()
if self._device_data.name == "Digital Discovery":
device.close(self._device_data)
raise Exception("This is a Digital Discovery, not an Analog Discovery.")
[docs]
def close(self):
"""
Disconnect from the board.
"""
device.close(self._device_data)
[docs]
def scope_open(
self,
sampling_frequency: float = 20e6,
buffer_size: int = 0,
offset: float = 0,
amplitude_range: float = 5,
):
"""
Initialize the oscilloscope.
Parameters
----------
sampling_frequency : float, default: 20e6
Sampling frequency of the oscilloscope in Hz.
buffer_size : int, default: 0
Buffer size of the oscilloscope in data points, a value of 0 means maximum buffer size.
offset : int, default: 0
Offset voltage of the oscilloscope in Volts.
amplitude_range : int, default: 5
Amplitude range of the oscilloscope in Volts.
"""
scope.open(
self._device_data, sampling_frequency, buffer_size, offset, amplitude_range
)
[docs]
def scope_close(self):
"""
Close (reset) the oscilloscope.
"""
scope.close(self._device_data)
[docs]
def scope_measure(self, channel: int = 1):
"""
Measure the voltage on a channel.
Parameters
----------
channel : int {1, 2}
The selected oscilloscope channel
Returns
-------
data : float
The measured voltage in Volts
"""
data = scope.measure(self._device_data, channel)
return data
[docs]
def scope_trigger(
self,
enable: bool,
source: str = "none",
channel: int = 1,
timeout: float = 0,
edge_rising: bool = True,
level: float = 0,
):
"""
Set up the triggering for the scope.
Parameters
----------
enable : bool
Enable or disable triggering.
source : str, default: "none"
Trigger source, possible: "none", "analog", "digital", "external_[1-4]" (ie: "external_1")
channel : int, default: 1
Trigger channel, possible options: 1-4 for analog, or 0-15 for digital
timeout : int, default: 0
Auto trigger timeout in seconds, default is 0
edge_rising : bool, default: True
Trigger edge rising - True means rising, False means falling, default is rising
level : int, default: 0
Trigger level in Volts, default is 0V
"""
if source == "none":
source = scope.trigger_source.none
elif source == "analog":
source = scope.trigger_source.analog
elif source == "digital":
source = scope.trigger_source.digital
elif source.startswith("external_"):
channel = int(source.split("_")[1])
source = scope.trigger_source.external[channel]
scope.trigger(
self._device_data, enable, source, channel, timeout, edge_rising, level
)
[docs]
def scope_record(self, channel: int = 1):
"""
Record an analog signal over a period of time determined by the scope buffer and sample rate.
Parameters
----------
channel : int, default: 1
The selected oscilloscope channel
Returns
-------
data : list[float]
A list with the recorded voltages
"""
data = scope.record(self._device_data, channel)
return data
[docs]
def get_scope_settings(self):
"""
Get the scope's sample rate, buffer size, and max buffer size.
Returns
-------
info : dict
A dictionary with the scope info: sample_rate, buffer_size, max_buffer_size
"""
info = {
"sample_rate": scope.data.sampling_frequency,
"buffer_size": scope.data.buffer_size,
"max_buffer_size": scope.data.max_buffer_size,
}
return info
[docs]
def get_scope_sample_rate(self):
"""
Get the scope sample rate.
Returns
-------
sample_rate : float
The scope sample rate in Hz
"""
sample_rate = scope.data.sampling_frequency
return sample_rate
[docs]
def wavegen_generate(
self,
channel: int = 1,
function: str = "sine",
offset: float = 0,
frequency: float = 1e03,
amplitude: float = 1,
symmetry: float = 50,
wait: float = 0,
run_time: float = 0,
repeat: int = 0,
data: list[float] = [],
):
"""
Generate a waveform on a wavegen channel.
Parameters
----------
channel : int, default: 1
The selected wavegen channel
function : str, default: "sine"
The function (shape) of the generated waveform, possible:
"custom", "sine", "square", "triangle", "noise", "ds", "pulse",
"trapezium", "sine_power", "ramp_up", "ramp_down"
offset : float, default: 0
Offset voltage in Volts
frequency : float, default: 1e03
Frequency in Hz
amplitude : float, default: 1
Amplitude in Volts
symmetry : float, default: 50
Signal symmetry in percentage
wait : float, default: 0
Wait time in seconds
run_time : float, default: 0
Run time in seconds, default is infinite (0)
repeat : int, default: 0
Repeat count, default is infinite (0)
data : list[float], default: []
List of voltages, used only if function=custom
"""
if function == "custom":
function = wavegen.function.custom
elif function == "sine":
function = wavegen.function.sine
elif function == "square":
function = wavegen.function.square
elif function == "triangle":
function = wavegen.function.triangle
elif function == "noise":
function = wavegen.function.noise
elif function == "ds":
function = wavegen.function.ds
elif function == "pulse":
function = wavegen.function.pulse
elif function == "trapezium":
function = wavegen.function.trapezium
elif function == "sine_power":
function = wavegen.function.sine_power
elif function == "ramp_up":
function = wavegen.function.ramp_up
elif function == "ramp_down":
function = wavegen.function.ramp_down
wavegen.generate(
self._device_data,
channel,
function,
offset,
frequency,
amplitude,
symmetry,
wait,
run_time,
repeat,
data,
)
[docs]
def wavegen_close(self, channel: int = 0):
"""
Reset a wavegen channel, or all channels (channel=0).
Parameters
----------
channel : int, default: 0
The selected wavegen channel
"""
wavegen.close(self._device_data, channel)
[docs]
def wavegen_enable(self, channel: int = 1):
"""
Enable a wavegen channel, starting the waveform generation.
Parameters
----------
channel : int, default: 1
The selected wavegen channel
"""
wavegen.enable(self._device_data, channel)
[docs]
def wavegen_disable(self, channel: int = 1):
"""
Disable a wavegen channel, stopping the waveform generation.
Parameters
----------
channel : int, default: 1
The selected wavegen channel
"""
wavegen.disable(self._device_data, channel)