Welcome to DwfPy!

Overview

DwfPy is a Python package that allows you to access Digilent WaveForms devices via Python. It provides a low-level API with complete access to the Digilent WaveForms API, and also a simple but powerful high-level API, which allows you to configure WaveForms devices with a single statement.

For instance, to output a 1kHz sine-wave on a Analog Discovery 2, you can simply write:

import dwfpy as dwf

with dwf.AnalogDiscovery2() as device:
    print('Generating a 1kHz sine wave on WaveGen channel 1...')
    device.analog_output['ch1'].setup('sine', frequency=1e3, amplitude=1, start=True)
    input('Press Enter key to exit.')

Features

  • Pythonic abstraction of Digilent Waveforms API.

  • Low-level API with complete access to the Digilent Waveforms API.

  • Powerful high-level API that supports one-line configuration statements.

  • Supports all sub-modules, such as oscilloscope, arbitrary waveform generator, logic analyzer, pattern generator, digital I/O, and power supplies.

  • Works with all WaveForms devices, such as the Analog Discovery 2 or the Digital Discovery.

Installing DwfPy

You can install the dwfpy package from PyPI using pip:

pip install dwfpy

In order to use the DwfPy package, you need Python 3.6 or higher.

As DwfPy builds on top of the WaveForms API, you need to install the WaveForms software, which includes the required runtime components to access the WaveForms devices.

The source code for the DwfPy package can be found at GitHub at https://github.com/mariusgreuel/dwfpy.

Documentation

You can find the DwfPy user’s guide at https://dwfpy.readthedocs.io/.

Detailed information about the Digilent Waveforms API is available from the WaveForms.

Examples

You can find Python examples using DwfPy in the dwfpy GitHub repository at https://github.com/mariusgreuel/dwfpy/tree/main/examples.

Getting help

For issues with DwfPy, please visit the dwfpy GitHub issue tracker.

Working with Devices

Creating a Device Instance

To find and create an instance of a Digilent Waveforms device, you simply create an instance of the Device class. You should use the Python with statement to ensure that your device is closed automatically on exit, or when an exception occurs:

import dwfpy as dwf

with dwf.Device() as device:
    print(f'Found device: {device.name} ({device.serial_number})')

Using a Specific Device Instance

If you know the kind of device you have connected to your PC, you can use a specialized class, such as AnalogDiscovery2 or DigitalDiscovery, which provides additional functions that are specific to that device:

import dwfpy as dwf

with dwf.AnalogDiscovery2() as device:
    print(f'Found an Analog Discovery 2: {device.user_name} ({device.serial_number})')

You can find the available device classes in the device module.

Filtering Devices

If you have multiple devices installed, you can pass additional filter parameters, while creating the device instance. For example, you can pass a serial number to pick a specific device:

import dwfpy as dwf

with dwf.Device(serial_number='123456ABCDEF') as device:
    print(f'Found a device with matching serial number: {device.user_name} ({device.serial_number})')

You can find additional filter parameters in the class constructor method Device.

Enumerating Devices

If you want to enumerate all present devices and get the devices properties, you can use the Device.enumerate() function to enumerate devices:

import dwfpy as dwf

for device in dwf.Device.enumerate():
    print(f'Found device: {device.name} {device.serial_number}')

For a complete example, see examples/device_enumeration.py and examples/device_info.py

Using the Oscilloscope

If your device has an oscilloscope, you can access it via the property analog_input. Individual channels can be accessed via a zero-based index, or a label, such as ‘ch1’.

There are various high-level functions that you can use to control the oscilloscope:

Channel Setup

  • analog_input[ch].setup() - Sets up the channel for data acquisition.

Data Acquisition

Triggering

Setting Up Channels

You can use the analog_input[ch].setup() function on a respective analog input channel to setup a channel. You can specify the channel voltage range, offset voltage, and more.

import dwfpy as dwf

with dwf.Device() as device:
    scope = device.analog_input
    scope[0].setup(range=5.0, offset=2.0)

Getting the Current ADC Sample

You can use the analog_input[ch].get_sample() function on a respective analog input channel to get the current ADC reading.

Note

You need to call the analog_input.read_status() function before calling get_sample() to read a sample from the device.

import dwfpy as dwf

with dwf.Device() as device:
    scope = device.analog_input
    scope[0].setup(range=50.0)
    scope.configure()
    scope.read_status()
    print(f'CH1: {scope[0].get_sample()}V')

For a complete example, see examples/analog_in_sample.py.

Single Data Acquisition

You can use the single() function on the analog input unit to perform a single-shot data acquisition. To configure the oscilloscope, pass the parameter configure=True. To start the acquisition immediately and wait for the acquisition to finish, pass the parameter start=True.

import dwfpy as dwf

with dwf.Device() as device:
    scope = device.analog_input
    scope[0].setup(range=5.0)
    scope.single(sample_rate=1e6, buffer_size=4096, configure=True, start=True)
    samples = scope[0].get_data()
    print(samples)

For a complete example, see examples/analog_in_single.py.

Recording Samples

You can use the record() function on the analog input unit to perform data recording. To configure the oscilloscope, pass the parameter configure=True. To start the acquisition immediately, pass the parameter start=True.

import dwfpy as dwf

with dwf.Device() as device:
    scope = device.analog_input
    scope[0].setup(range=5.0)
    recorder = scope.record(sample_rate=1e6, sample_count=1e6, configure=True, start=True)
    samples = recorder.channels[0].data_samples
    print(samples)

For a complete example, see examples/analog_in_record.py.

Setting Up an Edge Trigger

You can use the setup_edge_trigger() function to trigger the oscilloscope on a positive or negative slope of the waveform.

import dwfpy as dwf

with dwf.Device() as device:
    scope[0].setup(range=5.0)
    scope.setup_edge_trigger(mode='normal', channel=0, slope='rising', level=0.5, hysteresis=0.01)
    scope.single(sample_rate=1e6, buffer_size=4096, configure=True, start=True)
    samples = scope[0].get_data()

For a complete example, see examples/analog_in_single.py.

Using the Waveform Generator

If your device has an arbitrary waveform generator, you can access it via the property analog_output. Individual channels can be accessed via a zero-based index, or a label, such as ‘ch1’.

There are various high-level functions that you can use to control the waveform generator:

Channel Setup

  • analog_output[ch].setup() - Sets up a new carrier waveform.

  • analog_output[ch].setup_am() - Applies an AM modulation to a waveform.

  • analog_output[ch].setup_fm() - Applies an FM modulation to a waveform (sweep).

Setting up a new Waveform

You can use the setup() function to create a new waveform. You can specify the generator function, frequency, amplitude, offset, symmetry, and phase. By default, the carrier node is enabled via the parameter enabled=True. To start the channel immediately, pass the parameter start=True.

import dwfpy as dwf

with dwf.Device() as device:
    wavegen = device.analog_output
    wavegen[0].setup(function='sine', frequency=1e3, amplitude=1.0, start=True)

For a complete example, see examples/analog_out_sine.py.

Setting up an Amplitude Modulation

You can use the setup_am() function to modulate the amplitude. You can specify the generator function, frequency, amplitude, offset, symmetry, and phase, similar to the setup() function. By default, the AM node is enabled via the parameter enabled=True. To start the channel immediately, pass the parameter start=True.

import dwfpy as dwf

with dwf.Device() as device:
    wavegen = device.analog_output
    wavegen[0].setup(function='sine', frequency=1e3, amplitude=1.0)
    wavegen[0].setup_am(function='triangle', frequency=10, amplitude=50, start=True)

Setting up a Frequency Modulation

You can use the setup_fm() function to modulate the frequency. You can specify the generator function, frequency, amplitude, offset, symmetry, and phase, similar to the setup() function. By default, the FM node is enabled via the parameter enabled=True. To start the channel immediately, pass the parameter start=True.

import dwfpy as dwf

with dwf.Device() as device:
    wavegen = device.analog_output
    wavegen[0].setup(function='sine', frequency=1e3, amplitude=1.0)
    wavegen[0].setup_fm(function='sine', frequency=10, amplitude=10, start=True)

Using the Logic Analyzer

If your device has a logic analyzer, you can access it via the property digital_input. Individual channels can be accessed via a zero-based index, or a label, such as ‘ch1’.

There are various high-level functions that you can use to control the logic analyzer:

Acquisition

Triggering

Channel specific Triggering

  • digital_input[ch].setup_trigger() - Sets up the trigger condition for this channel.

  • digital_input[ch].setup_reset_trigger() - Sets up the trigger reset condition for this channel.

Single Data Acquisition

You can use the single() function on the digital input unit to perform a single-shot data acquisition. To configure the logic analyzer, pass the parameter configure=True. To start the acquisition immediately and wait for the acquisition to finish, pass the parameter start=True.

import dwfpy as dwf

with dwf.Device() as device:
    logic = device.digital_input
    samples = logic.single(sample_rate=1e6, buffer_size=4096, configure=True, start=True)
    print(samples)

For a complete example, see examples/digital_in_acquisition.py.

Recording Samples

You can use the record() function on the digital input unit to perform data recording. To configure the logic analyzer, pass the parameter configure=True. To start the acquisition immediately, pass the parameter start=True.

import dwfpy as dwf

with dwf.Device() as device:
    logic = device.digital_input
    recorder = logic.record(sample_rate=1e6, sample_count=1e6, configure=True, start=True)
    samples = recorder.data_samples
    print(samples)

For a complete example, see examples/digital_in_record.py.

For an examples that uses data compression, see examples/digital_in_record_compress.py.

Using the Pattern Generator

If your device has a pattern generator, you can access it via the property digital_output. Individual channels can be accessed via a zero-based index, or a label, such as ‘ch1’.

There are various functions that you can use to configure pins:

  • digital_output[ch].setup_constant() - Sets up the channel as a constant output.

  • digital_output[ch].setup_clock() - Sets up the channel as a clock output.

  • digital_output[ch].setup_pulse() - Sets up the channel as a pulse output.

  • digital_output[ch].setup_random() - Sets up the channel as a random output.

  • digital_output[ch].setup_custom() - Sets up the channel with a custom output.

All setup function allow you to speficy the output mode (‘push-pull’, ‘open-drain’, ‘open-source’, or ‘three-state’) and the idle state (‘init’, ‘low’, ‘high’, or ‘z’).

Setting up a Constant Output

You can use the setup_constant() function to drive a channel with a constant output value. To start the channel immediately, pass the parameter start=True.

import dwfpy as dwf

with dwf.Device() as device:
    pattern = device.digital_output
    # Output a high level on pin DIO-0.
    pattern[0].setup_constant(value=True, start=True)

Setting up a Clock Signal

You can use the setup_clock() function to output a clock or PWM signal. You can specify the frequency, duty_cycle, phase, delay, and repetition count. By default, the channel is enabled via the parameter enabled=True. To start the channel immediately, pass the parameter start=True.

import dwfpy as dwf

with dwf.Device() as device:
    pattern = device.digital_output
    # Output a 1kHz clock on pin DIO-0.
    pattern[0].setup_clock(frequency=1e3, start=True)

For a complete example, see examples/digital_out_clock.py.

Setting up a Random Signal

You can use the setup_random() function to output a clock or PWM signal. You can specify the rate, delay, and repetition count. By default, the channel is enabled via the parameter enabled=True. To start the channel immediately, pass the parameter start=True.

import dwfpy as dwf

with dwf.Device() as device:
    pattern = device.digital_output
    # Output a random pattern at a 1kHz rate on pin DIO-0.
    pattern[0].setup_random(rate=1e3, start=True)

For a complete example, see examples/digital_out_pins.py.

Using the Digital I/O

If your device has Digital I/O, you can access it via the property digital_io. Individual channels can be accessed via a zero-based index, or a label, such as ‘dio0’, ‘dio1’, etc.

Reading from Inputs

By default, pins are configured as inputs.

Note

You need to call the digital_io.read_status() function before reading from input_state to read all input states from the device.

import dwfpy as dwf

with dwf.Device() as device:
    io = device.digital_io
    # Configure DIO-0 as input
    io[0].setup(enabled=False, configure=True)
    # Read all I/O pins
    io.read_status()
    dio0 = io[1].input_state
    print(f'DIO-0={dio0}')

Writing to Outputs

You can use the setup() function to setup a Digital I/O pin. To configure the Digital I/O pins, pass the parameter configure=True.

import dwfpy as dwf

with dwf.Device() as device:
    io = device.digital_io
    # Configure DIO-0 as output, and set the state to high
    io[0].setup(enabled=True, state=True)
    # Configure DIO-1 as output, and set the state to low
    io[1].setup(enabled=True, state=False, configure=True)
    # Output a one on DIO-1
    io[1].output_state = True

For a complete example, see examples/digital_in_acquisition.py.

Using the Power Supplies

Depending on the device you have, there may be one or more power supplies that can be accessed via the channels and nodes of the Analog IO module, which are documented in the WaveForms.

Note

In order to output a voltage, most power supplies have to be enabled both individually and via the master enable switch.

For instance, to output 3.3V on the positive power supply of an Analog Discovery 2, you can write:

import dwfpy as dwf

with dwf.Device() as device:
    # Set the voltage of the positive power supply to 3.3V.
    device.analog_io[0][1].value = 3.3

    # Enable the positive power supply.
    device.analog_io[0][0].value = True

    # Enable the master-enable switch.
    device.analog_io.master_enable = True

Instead of the generic Device class, you can also use the specialized device classes, which simplifies the access to the power supplies.

For instance, to output 3.3V on the positive power supply of an Analog Discovery 2, you can write:

import dwfpy as dwf

with dwf.AnalogDiscovery2() as device:
    # Set the positive power supply to 3.3V and enable it.
    device.supplies.positive.setup(voltage=3.3)
    device.supplies.master_enable = True

Labelling Channels

Instead of indexing channels via a zero-based integer index, the channels can be accessed via a label. By default, analog channels are labelled ch1, ch2, etc. Digital channels are labelled dio0, dio1, etc.

You can rename the channel labels to you needs. For instance, if you have a clock signal on DIO pin 0 that you want to access via the label clock, you could write:

import dwfpy as dwf

with dwf.Device() as device:
    # Rename pin DIO-0 from 'dio0' to 'clock'
    device.digital_output[0].label = 'clock'

    # DIO-0 can now be referenced via the label 'clock'
    device.digital_output['clock'].setup_clock(frequency=1e3)

dwfpy Design

The design of the dwfpy package consists of three layers: Python bindings, low-level API, and high-level API.

Bindings

The dwfpy bindings give you raw access to the C API of the DWF DLL. In order to provide a natural look-and-feel while working in Python, the following API changes have been made:

  • The naming of the DWF API has been adapted to match Python naming conventions.

  • The DWF function API has been declared using the ctypes CFUNCTYPE prototypes, which allow you to pass Python types such as int, instead of ctypes types such as c_int.

  • Error handling is performed automatically, i.e. the C functions return value is checked and an exception is thrown when appropriate.

  • Function return values are directly returned as a scalar or tuple.

For instance, the DWF C API to get the minimum and maximum offset voltage of an analog-out channel is:

int FDwfAnalogOutNodeOffsetInfo(HDWF hdwf, int idxChannel, AnalogOutNode node, double *pMin, double *pMax);

When working with the dwfpy bindings, it can be used as follows:

import dwfpy as dwf
import dwfpy.bindings as dwfb

with dwf.Device() as device:
    min_offset, max_offset = dwfb.dwf_analog_out_node_offset_info(device.handle, 0, dwfb.ANALOG_OUT_NODE_CARRIER)

Typically, you do not work with the dwfpy bindings. Instead, use the low-level or high-level API.

Low-Level API

The dwfpy low-level API is designed to map the bindings to a more structured API.

  • Functional units of the device have been grouped into objects.

  • Channels and nodes are abstracted as collections, using a dict-like API.

For instance, the DWF C API to set the offset voltage of the analog_out channel 1 (arbitary waveform generator) is:

int FDwfAnalogOutNodeOffsetSet(HDWF hdwf, int idxChannel, AnalogOutNode node, double vOffset);

When working with the dwfpy low-level API, it can be used as follows:

import dwfpy as dwf

with dwf.Device() as device:
    device.analog_output.channels[0].nodes[dwf.AnalogOutputNode.CARRIER].offset = 1.23

High-Level API

The dwfpy high-level API is designed to perform multiple common actions with a single line of Python code.

For instance, to start channel 1 of the arbitary waveform generator to output a sine-wave, using a frequency of 1kHz and an amplitude of 1Vpp, you would write:

import dwfpy as dwf

with dwf.Device() as device:
    device.analog_output['ch1'].setup(function='sine', frequency=1e3, amplitude=1, start=True)

dwfpy

dwfpy is a package for accessing Digilent WaveForms devices.