Skip to content

Digilent Analog Discovery

Driver for the Digilent Analog Discovery 2 and Analog Discovery 3. A single connector exposes the device's oscilloscope, arbitrary waveform generator, power supplies, and 16-line digital I/O. Talks to the device through Digilent's WaveForms SDK (libdwf) via the pydwf Python wrapper.

Tier-2 free driver — distributed as a .muxdriver Python package.

Prerequisites

Install Digilent WaveForms on the host machine. It ships the native libdwf library that pydwf loads at runtime — without it the driver fails to initialise with a link to the download.

The pydwf Python package itself is installed automatically into the driver's per-driver venv on first activation; you do not need to pip install anything yourself.

Connector config

javascript
export default {
  driver: "Digilent Analog Discovery",
  config: {
    device_index: 0,           // Optional. Default 0 (first connected device).
    // device_serial: "210321ABCDEF",  // Optional. Open a specific device by serial.
  },
  methods: {
    acquire: { fn: () => driver.acquire() },
    gen_apply: { fn: (channel = 1) => driver.gen_apply({ channel }) },
  },
};

If both device_serial and device_index are set, device_serial wins.

Quick examples

Capture a waveform

javascript
const frame = await connector("scope").acquire();
// frame = { time: [...], ch1: [...], ch2: [...], sample_rate, samples, ts }
plot(frame.time, frame.ch1);

Generate a sine wave on W1, capture it on Ch1

javascript
const ad = connector("ad");

// Stage AWG settings, then push them.
ad.gen_ch1_function = "sine";
ad.gen_ch1_frequency = 1000;
ad.gen_ch1_amplitude = 1.0;
ad.gen_ch1_enabled = true;
await ad.gen_apply(1);

// Now capture and plot.
const { time, ch1 } = await ad.acquire();

Drive the power supplies

javascript
const ad = connector("ad");
ad.psu_pos_enabled = true;
ad.psu_neg_enabled = true;
ad.psu_pos_voltage = 3.3;       // AD3 only; no-op on AD2 with a warning
ad.psu_master_enabled = true;   // gates both rails

Stream live scope frames

javascript
const ad = connector("ad");
on("waveform", (frame) => plot(frame.time, frame.ch1));
await ad.acquire_continuous();
// ...
await ad.stop_continuous();

Properties

Identity (read-only)

PropertyTypeDescription
device_namestringe.g. "Analog Discovery 2" or "Analog Discovery 3"
serial_numberstringDevice serial
device_revisionstringHardware revision (currently the device name)

Oscilloscope (osc_*)

PropertyTypeAccessUnitDescription
osc_sample_ratedoubleR/WHzSample rate
osc_buffer_sizeintR/WsamplesSamples per channel per acquisition
osc_ch1_enabled, osc_ch2_enabledboolR/WPer-channel enable
osc_ch1_range, osc_ch2_rangedoubleR/WVppInput range (peak-to-peak)
osc_ch1_offset, osc_ch2_offsetdoubleR/WVInput offset
osc_trigger_sourcestringR/Wnone, ch1, ch2, ext1, ext2
osc_trigger_leveldoubleR/WVTrigger level
osc_trigger_edgestringR/Wrising or falling
osc_ch1_waveform, osc_ch2_waveformdouble[]RVLast captured samples

Scope settings are staged in software and pushed to the device at the start of every acquire(), so the order of property writes doesn't matter.

Function generator (gen_chN_*)

Repeat for gen_ch1_* and gen_ch2_*:

PropertyTypeAccessUnitDescription
gen_ch1_enabledboolR/WOutput enable (staged)
gen_ch1_functionstringR/WOne of dc, sine, square, triangle, rampup, rampdown, noise, pulse
gen_ch1_frequencydoubleR/WHzOutput frequency
gen_ch1_amplitudedoubleR/WVPeak amplitude
gen_ch1_offsetdoubleR/WVDC offset
gen_ch1_phasedoubleR/WdegPhase
gen_ch1_symmetrydoubleR/W%Duty / symmetry (0-100)

Generator settings are staged; call gen_apply({ channel }) to push them to the hardware and start (or stop) the output.

Power supplies (psu_*)

PropertyTypeAccessUnitDescription
psu_master_enabledboolR/WMaster enable for both rails
psu_pos_enabledboolR/WV+ rail enable
psu_pos_voltagedoubleR/WVV+ rail voltage (programmable on AD3 only)
psu_neg_enabledboolR/WV- rail enable
psu_neg_voltagedoubleR/WVV- rail voltage (programmable on AD3 only)

AD2 vs AD3

The AD2 supplies are fixed at ±5 V — writing psu_pos_voltage or psu_neg_voltage on an AD2 logs a warning and is a no-op so the same connector config works on both devices. On an AD3 the writes take effect.

Digital I/O (dio_*)

PropertyTypeAccessDescription
dio_output_enable_maskintR/W16-bit mask: bit set = pin is an output
dio_output_valueintR/W16-bit mask: value driven on output pins
dio_input_valueintR16-bit mask: value read from all pins

Static digital I/O only. Timed capture (logic analyzer) and timed output (pattern generator) are not yet exposed.

Actions

ActionArgsDescription
acquire()Single-shot capture of both channels. Returns { time, ch1, ch2, sample_rate, samples, ts }. Also emits the same payload on the waveform stream and updates osc_chN_waveform.
acquire_continuous()Start a background loop that captures repeatedly and emits each frame on waveform. Idempotent — calling it again while running is a no-op.
stop_continuous()Stop the continuous acquisition loop.
gen_apply({ channel })channel: 1 or 2Push the staged generator settings for one channel to the hardware. Starts the output if gen_chN_enabled is true, stops it otherwise.
reset()Reset every subsystem and restore default scope / generator / supplies / DIO state.

Streams

StreamPayload
waveform{ time, ch1, ch2, sample_rate, samples, ts } — emitted by every acquire() (single-shot or continuous).

Limitations

  • Logic analyzer / pattern generator not exposed. Static DIO only.
  • Spectrum analyzer / voltmeter modes are not exposed as first-class properties. Acquire raw samples and post-process them in your script.
  • Single device per connector. Multiple AD2/AD3s connected at once require one connector per device (use device_serial to pin them).
  • External trigger sources (ext1, ext2) require the corresponding trigger I/O to be wired up; refer to the WaveForms documentation.

Muxit — Hardware Orchestration Platform