Skip to content

Commit

Permalink
Create DigiAD2.py
Browse files Browse the repository at this point in the history
Included library DigiAD2.py, a driver that might let us interact with the Analog Discovery 2 better.

Sources: Digilent/WaveForms-SDK-Getting-Started-PY#1

https://github.com/ICE-QTM/QTMtoolbox/blob/master/instruments/DigiAD2.py
  • Loading branch information
smbdoggy83 committed Nov 1, 2022
1 parent 0e74a3f commit 4382fb5
Showing 1 changed file with 394 additions and 0 deletions.
394 changes: 394 additions & 0 deletions Python Waveforms Test/WF_SDK/DigiAD2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,394 @@
# -*- coding: utf-8 -*-
"""
Module to interact with a Digilent Analog Discovery 2.
Uses the WaveForms SDK to communicate to the USB device.
Version 1.0 (2022-09-30)
Daan Wielens - Researcher at ICE/QTM
University of Twente
[email protected]
"""

import numpy as np
import ctypes
from sys import path

# Load the dynamic library of the Waveform SDK
dwf = ctypes.cdll.dwf
constants_path = r"C:\Program Files (x86)\Digilent\WaveFormsSDK\samples\py"
path.append(constants_path)
import dwfconstants as constants

class DigiAD2:
type = 'Digilent Analog Discovery 2'

def __init__(self, config=0):
'''Initialize connection to the device'''
self.handle = ctypes.c_int()
self.config = config
dwf.FDwfDeviceConfigOpen(ctypes.c_int(-1), ctypes.c_int(self.config), ctypes.byref(self.handle))
# Config 0 : Scope 8k , Wavegen 4k
# Config 1 : Scope 16k , Wavegen 1k

def close(self):
'''Close connection to the device'''
dwf.FDwfDeviceClose(self.handle)
self.handle = ctypes.c_int(0)


def open_scope(self, freq=20E6, npoints=8192, offset=0, amp_range=5):
'''
Initialize connection to the scope, write acquisition settings
Parameters: - freq: sampling frequency in Hz, default = 20 MHz
- npoints: number of points, default = 8192
- offset: offset voltage in V, default = 0 V
- amp_range: amplitude range in V, default = +/- 5 V
'''
# Enable all channels
dwf.FDwfAnalogInChannelEnableSet(self.handle, ctypes.c_int(0), ctypes.c_bool(True))

# Set offset voltage (in Volts)
dwf.FDwfAnalogInChannelOffsetSet(self.handle, ctypes.c_int(0), ctypes.c_double(offset))

# Set range (maximum signal amplitude in Volts)
dwf.FDwfAnalogInChannelRangeSet(self.handle, ctypes.c_int(0), ctypes.c_double(amp_range))

# Set the buffer size (data point in a recording)
dwf.FDwfAnalogInBufferSizeSet(self.handle, ctypes.c_int(int(npoints)))

# Set the acquisition frequency (in Hz)
dwf.FDwfAnalogInFrequencySet(self.handle, ctypes.c_double(freq))

# Disable averaging (for more info check the documentation)
dwf.FDwfAnalogInChannelFilterSet(self.handle, ctypes.c_int(-1), constants.filterDecimate)
self.freq = freq
self.buffer = int(npoints)

def close_scope(self):
'''Reset the scope'''
dwf.FDwfAnalogInReset(self.handle)

def read_volt1(self):
'''Measure the voltage of channel 1'''
# Set up the instrument
dwf.FDwfAnalogInConfigure(self.handle, ctypes.c_bool(False), ctypes.c_bool(False))
# Read data to an internal buffer
dwf.FDwfAnalogInStatus(self.handle, ctypes.c_bool(False), ctypes.c_int(0))
# Extract data from that buffer
voltage = ctypes.c_double() # variable to store the measured voltage
dwf.FDwfAnalogInStatusSample(self.handle, ctypes.c_int(0), ctypes.byref(voltage))
# Store the result as float
voltage = voltage.value
return voltage

def read_volt2(self):
'''Measure the voltage of channel 2'''
# Set up the instrument
dwf.FDwfAnalogInConfigure(self.handle, ctypes.c_bool(False), ctypes.c_bool(False))
# Read data to an internal buffer
dwf.FDwfAnalogInStatus(self.handle, ctypes.c_bool(False), ctypes.c_int(0))
# Extract data from that buffer
voltage = ctypes.c_double() # variable to store the measured voltage
dwf.FDwfAnalogInStatusSample(self.handle, ctypes.c_int(1), ctypes.byref(voltage))
# Store the result as float
voltage = voltage.value
return voltage

def get_wav1(self):
"""
Record V(t) for a number of 'npoints' (as defined in open_scope)
Returns: - time : list of timestamps in s
- voltages: list of voltages in V
"""
# set up the instrument
dwf.FDwfAnalogInConfigure(self.handle, ctypes.c_bool(False), ctypes.c_bool(True))

# read data to an internal buffer
while True:
status = ctypes.c_byte() # variable to store buffer status
dwf.FDwfAnalogInStatus(self.handle, ctypes.c_bool(True), ctypes.byref(status))

# check internal buffer status
if status.value == constants.DwfStateDone.value:
# exit loop when ready
break

# copy buffer
buffer = (ctypes.c_double * self.buffer)() # create an empty buffer
dwf.FDwfAnalogInStatusData(self.handle, ctypes.c_int(0), buffer, ctypes.c_int(self.buffer))

# calculate aquisition time
time = range(0, self.buffer)
time = [moment / self.freq for moment in time]

# convert into list
voltages = [float(element) for element in buffer]
return time, voltages

def get_wav2(self):
"""
Record V(t) for a number of 'npoints' (as defined in open_scope)
Returns: - time : list of timestamps in s
- voltages: list of voltages in V
"""
# set up the instrument
dwf.FDwfAnalogInConfigure(self.handle, ctypes.c_bool(False), ctypes.c_bool(True))

# read data to an internal buffer
while True:
status = ctypes.c_byte() # variable to store buffer status
dwf.FDwfAnalogInStatus(self.handle, ctypes.c_bool(True), ctypes.byref(status))

# check internal buffer status
if status.value == constants.DwfStateDone.value:
# exit loop when ready
break

# copy buffer
buffer = (ctypes.c_double * self.buffer)() # create an empty buffer
dwf.FDwfAnalogInStatusData(self.handle, ctypes.c_int(1), buffer, ctypes.c_int(self.buffer))

# calculate aquisition time
time = range(0, self.buffer)
time = [moment / self.freq for moment in time]

# convert into list
voltages = [float(element) for element in buffer]
return time, voltages

def trigger(self, enable=True, source='analog', channel=0, timeout=0, edge_rising=True, level=0, hysteresis=0.05):
'''
Set up triggering for the scope
Parameters: - enable trigger?: bool
- trigger source: none, analog, digital, external[1-4]
- trigger channel: 1-4 for analog, 0-15 for digital
- timeout: seconds (default = 0)
- trigger edge rising: true = rising, false = falling (default rising)
- trigger level: volts (default = 0)
- hysteresis: hysteresis voltage level for reset (default = 50 mV)
'''
# Translate options to constants
if source == 'none':
source = constants.trigsrcNone
elif source == 'analog':
source = constants.trigsrcDetectorAnalogIn
elif source == 'digital':
source = constants.trigsrcDetectorDigitalIn
else:
raise ValueError('This has not been implemented yet, sorry.')

if enable and source != constants.trigsrcNone:
# enable/disable auto triggering
dwf.FDwfAnalogInTriggerAutoTimeoutSet(self.handle, ctypes.c_double(timeout))

# set trigger source
dwf.FDwfAnalogInTriggerSourceSet(self.handle, source)

# set trigger channel
if source == constants.trigsrcDetectorAnalogIn:
channel -= 1 # decrement analog channel index
dwf.FDwfAnalogInTriggerChannelSet(self.handle, ctypes.c_int(channel))

# set trigger type
dwf.FDwfAnalogInTriggerTypeSet(self.handle, constants.trigtypeEdge)

# set trigger level
dwf.FDwfAnalogInTriggerLevelSet(self.handle, ctypes.c_double(level))

# set hysteresis level
dwf.FDwfAnalogInTriggerHysteresisSet(self.handle, ctypes.c_double(hysteresis))

# set trigger edge
if edge_rising == True:
# rising edge
dwf.FDwfAnalogInTriggerConditionSet(self.handle, constants.trigcondRisingPositive)
elif edge_rising == False:
# falling edge
dwf.FDwfAnalogInTriggerConditionSet(self.handle, constants.trigcondFallingNegative)
self.triggering = True
else:
# turn off the trigger
dwf.FDwfAnalogInTriggerSourceSet(self.handle, constants.trigsrcNone)
self.triggering = False
return

def set_wav1(self, function, frequency=1e03, amplitude=1, offset=0, symmetry=50, wait=0, run_time=0, repeat=0, data=[]):
"""
generate an analog signal
parameters: - device data
- function - possible: custom, sine, square, triangle, noise, ds, pulse, trapezium, sine_power, ramp_up, ramp_down
- frequency in Hz, default is 1KHz
- amplitude in Volts, default is 1V
- offset voltage in Volts
- signal symmetry in percentage, default is 50%
- wait time in seconds, default is 0s
- run time in seconds, default is infinite (0)
- repeat count, default is infinite (0)
- data - list of voltages, used only if function=custom, default is empty
"""
if function == 'custom':
function = constants.funcCustom
elif function == 'sine':
function = constants.funcSine
elif function == 'square':
function = constants.funcSquare
elif function == 'triangle':
function = constants.funcTriangle
elif function == 'noise':
function = constants.funcNoise
elif function == 'dc':
function = constants.funcDC
elif function == 'pulse':
function = constants.funcPulse
elif function == 'trapezium':
function = constants.funcTrapezium
elif function == 'sine_power':
function = constants.funcSinePower
elif function == 'ramp_up':
function = constants.funcRampUp
elif function == 'ramp_down':
function = constants.funcRampDn
else:
print('Warning. We will try to pass along your function directly.')

# enable channel
channel = ctypes.c_int(0)
dwf.FDwfAnalogOutNodeEnableSet(self.handle, channel, constants.AnalogOutNodeCarrier, ctypes.c_bool(True))

# set function type
dwf.FDwfAnalogOutNodeFunctionSet(self.handle, channel, constants.AnalogOutNodeCarrier, function)

# load data if the function type is custom
if function == constants.funcCustom:
data_length = len(data)
buffer = (ctypes.c_double * data_length)()
for index in range(0, len(buffer)):
buffer[index] = ctypes.c_double(data[index])
dwf.FDwfAnalogOutNodeDataSet(self.handle, channel, constants.AnalogOutNodeCarrier, buffer, ctypes.c_int(data_length))

# set frequency
dwf.FDwfAnalogOutNodeFrequencySet(self.handle, channel, constants.AnalogOutNodeCarrier, ctypes.c_double(frequency))

# set amplitude or DC voltage
dwf.FDwfAnalogOutNodeAmplitudeSet(self.handle, channel, constants.AnalogOutNodeCarrier, ctypes.c_double(amplitude))

# set offset
dwf.FDwfAnalogOutNodeOffsetSet(self.handle, channel, constants.AnalogOutNodeCarrier, ctypes.c_double(offset))

# set symmetry
dwf.FDwfAnalogOutNodeSymmetrySet(self.handle, channel, constants.AnalogOutNodeCarrier, ctypes.c_double(symmetry))

# set running time limit
dwf.FDwfAnalogOutRunSet(self.handle, channel, ctypes.c_double(run_time))

# set wait time before start
dwf.FDwfAnalogOutWaitSet(self.handle, channel, ctypes.c_double(wait))

# set number of repeating cycles
dwf.FDwfAnalogOutRepeatSet(self.handle, channel, ctypes.c_int(repeat))

# start
dwf.FDwfAnalogOutConfigure(self.handle, channel, ctypes.c_bool(True))

def set_wav2(self, function, frequency=1e03, amplitude=1, offset=0, symmetry=50, wait=0, run_time=0, repeat=0, data=[]):
"""
generate an analog signal
parameters: - device data
- function - possible: custom, sine, square, triangle, noise, ds, pulse, trapezium, sine_power, ramp_up, ramp_down
- frequency in Hz, default is 1KHz
- amplitude in Volts, default is 1V
- offset voltage in Volts
- signal symmetry in percentage, default is 50%
- wait time in seconds, default is 0s
- run time in seconds, default is infinite (0)
- repeat count, default is infinite (0)
- data - list of voltages, used only if function=custom, default is empty
"""
if function == 'custom':
function = constants.funcCustom
elif function == 'sine':
function = constants.funcSine
elif function == 'square':
function = constants.funcSquare
elif function == 'triangle':
function = constants.funcTriangle
elif function == 'noise':
function = constants.funcNoise
elif function == 'dc':
function = constants.funcDC
elif function == 'pulse':
function = constants.funcPulse
elif function == 'trapezium':
function = constants.funcTrapezium
elif function == 'sine_power':
function = constants.funcSinePower
elif function == 'ramp_up':
function = constants.funcRampUp
elif function == 'ramp_down':
function = constants.funcRampDn
else:
print('Warning. We will try to pass along your function directly.')

# enable channel
channel = ctypes.c_int(1)
dwf.FDwfAnalogOutNodeEnableSet(self.handle, channel, constants.AnalogOutNodeCarrier, ctypes.c_bool(True))

# set function type
dwf.FDwfAnalogOutNodeFunctionSet(self.handle, channel, constants.AnalogOutNodeCarrier, function)

# load data if the function type is custom
if function == constants.funcCustom:
data_length = len(data)
buffer = (ctypes.c_double * data_length)()
for index in range(0, len(buffer)):
buffer[index] = ctypes.c_double(data[index])
dwf.FDwfAnalogOutNodeDataSet(self.handle, channel, constants.AnalogOutNodeCarrier, buffer, ctypes.c_int(data_length))

# set frequency
dwf.FDwfAnalogOutNodeFrequencySet(self.handle, channel, constants.AnalogOutNodeCarrier, ctypes.c_double(frequency))

# set amplitude or DC voltage
dwf.FDwfAnalogOutNodeAmplitudeSet(self.handle, channel, constants.AnalogOutNodeCarrier, ctypes.c_double(amplitude))

# set offset
dwf.FDwfAnalogOutNodeOffsetSet(self.handle, channel, constants.AnalogOutNodeCarrier, ctypes.c_double(offset))

# set symmetry
dwf.FDwfAnalogOutNodeSymmetrySet(self.handle, channel, constants.AnalogOutNodeCarrier, ctypes.c_double(symmetry))

# set running time limit
dwf.FDwfAnalogOutRunSet(self.handle, channel, ctypes.c_double(run_time))

# set wait time before start
dwf.FDwfAnalogOutWaitSet(self.handle, channel, ctypes.c_double(wait))

# set number of repeating cycles
dwf.FDwfAnalogOutRepeatSet(self.handle, channel, ctypes.c_int(repeat))

# start
dwf.FDwfAnalogOutConfigure(self.handle, channel, ctypes.c_bool(True))

def close_wav1(self):
dwf.FDwfAnalogOutReset(self.handle, ctypes.c_int(0))

def close_wav2(self):
dwf.FDwfAnalogOutReset(self.handle, ctypes.c_int(1))

def get_version(self):
version = ctypes.create_string_buffer(16)
dwf.FDwfGetVersion(version)
return str(version.value)[2:-1]

def get_trig_slope(self):
resp = ctypes.c_int()
dwf.FDwfDeviceTriggerSlopeInfo(self.handle, resp)
return resp.value

def get_trig(self):
resp = ctypes.c_int()
dwf.FDwfAnalogInTriggerConditionInfo(self.handle, resp)
return resp.value

def set_trig_type(self, trig_type=0):
dwf.FDwfAnalogInTriggerConditionSet(self.handle, ctypes.c_int(trig_type))

0 comments on commit 4382fb5

Please sign in to comment.