NI DAQs

Overview

This labscript device is a master device that can control a wide range of NI Multi-function data acquistion devices.

Installation

This labscript device requires an installation of the NI-DAQmx module, available for free from NI.

The python bindings are provided by the PyDAQmx package, available through pip.

Adding a Device

While the NI_DAQmx device can be used directly by manually specifying the many necessary parameters, it is preferable to add the device via an appropriate subclass. This process is greatly simplified by using the get_capabilities.py script followed by the generate_subclasses.py script.

To add support for a DAQmx device that is not yet supported, run get_capabilities.py on a computer with the device in question connected (or with a simulated device of the correct model configured in NI-MAX). This will introspect the capabilities of the device and add those details to capabilities.json. To generate labscript device classes for all devices whose capabilities are known, run generate_subclasses.py. Subclasses of NI_DAQmx will be made in the models subfolder, and they can then be imported into labscript code with:

from labscript_devices.NI_DAQmx.labscript_devices import NI_PCIe_6363

or similar. The class naming is based on the model name by prepending “NI_” and replacing the hyphen with an underscore, i.e. ‘PCIe-6363’ -> NI_PCIe_6363.

Generating device classes requires the Python code-formatting library ‘black’, which can be installed via pip (Python 3.6+ only). If you don’t want to install this library, the generation code will still work, it just won’t be formatted well.

The current list of pre-subclassed devices is:

Usage

NI Multifunction DAQs generally provide hardware channels for StaticAnalogOut, StaticDigitalOut, AnalogOut, DigitalOut, and AnalogIn labscript quantities for use in experiments. Exact numbers of channels, performance, and configuration depend on the model of DAQ used.

from labscript import *

from labscript_devices.DummyPseudoclock.labscript_devices import DummyPseudoclock
from labscript_devices.NI_DAQmx.models.NI_USB_6343 import NI_USB_6343

DummyPseudoclock('dummy_clock',BLACS_connection='dummy')

NI_USB_6343(name='daq',parent_device=dummy_clock.clockline,
                        MAX_name='ni_usb_6343',
                        clock_terminal='/ni_usb_6343/PFI0',
                        acquisition_rate=100e3)

AnalogIn('daq_ai0',daq,'ai0')
AnalogIn('daq_ai1',daq,'ai1')

AnalogOut('daq_ao0',daq,'ao0')
AnalogIn('daq_ai1',daq,'ai1')

WaitMonitors

NI DAQs are also used within labscript to provide a WaitMonitor. When configured, the WaitMonitor allows for arbitrary-length pauses in experiment execution, waiting for some trigger to restart. The monitor provides a measurement of the duration of the wait for use in interpreting the resulting data from the experiment.

Configuration uses three digital I/O connections on the DAQ:

  • The parent_connection which sends pulses at the beginning of the experiment, the start of the wait, and the end of the wait.

  • The acquisition_connection which must be wired to a counter and measures the time between the pulses of the parent connection.

  • The timeout_connection which can send a restart pulse if the wait times out.

An example configuration of a WaitMonitor using a NI DAQ is shown here

# A wait monitor for AC-line triggering
# This requires custom hardware
WaitMonitor(name='wait_monitor',parent_device=daq,connection='port0/line0',
                        acquisition_device=daq, acquisition_connection='ctr0',
                        timeout_device=daq, timeout_connection='PFI1')
# Necessary to ensure even number of digital out lines in shot
DigitalOut('daq_do1',daq,'port0/line1')

Note that the counter connection is specified using the logical label 'ctr0'. On many NI DAQs, the physical connection to this counter is PFI9. The physical wiring for this configuration would have port0/line0 wired directly to PFI9, with PFI1 being sent to the master pseudoclock retriggering system in case of timeout. If timeouts are not expected/represent experiment failure, this physical connection can be omitted.

In addition to their external ports, some types of NI DAQ modules (PXI, PXIe, CompactDAQ) feature internal ports, known as “terminals” in NI terminology. Terminals include most clocks and triggers in a module, as well as the external PFIN connections. The buffered and static digital IO connections are not terminals. Connections between terminals can be used for sharing clocks or triggers between modules in the same chassis (note: if sufficient clocklines and external inputs are available, it is likely preferable to simply use a unique clockline for each card). Within labscript, there are two methods for accessing this functionality. For sharing the clock input signal to other cards, the clock_mirror_terminal argument in the constructor can be specified. For example, in a system with two PXI-6733 analog cards in a PXI chassis (which supports 8 internal triggers, named PXI_TrigN), the connection table entries are

NI_PXI_6733(name='dev_1',
                        ...,
                        clock_terminal='/Dev1/PFI0',
                        clock_mirror_terminal='/Dev1/PXI_Trig0',
                        MAX_name='Dev1')

NI_PXI_6733(name='dev_2',
                        ...,
                        clock_terminal='/Dev2/PXI_Trig0',
                        MAX_name='Dev2')

However, some NI DAQ modules can not be clocked from certain terminal. To determine this, consult the Device Routes tab in NI MAX. If there is not a Direct Route or Indirect Route between the clock source and clock destination, the best option is to choose a different clock_mirror_terminal if possible. For some combinations of modules, there will be no pair of triggers linked to all the cards. To handle this situation, two triggers can be linked using the connected_terminals argument. This argument takes a list of tuples of terminal names, and connects the first terminal to the second terminal. For example, to share the clock in the previous with an additional PXIe-6535 digital card (which can not use PXI_Trig0 as a clock), the connection table entries are

NI_PXI_6733(name='dev_1',
                        ...,
                        clock_terminal='/Dev1/PFI0',
                        clock_mirror_terminal='/Dev1/PXI_Trig0',
                        MAX_name='Dev1')

NI_PXI_6733(name='dev_2',
                        ...,
                        clock_terminal='/Dev2/PXI_Trig0',
                        MAX_name='Dev2')

NI_PXIe_6535(name='dev_3',
                        ...,
                        clock_terminal='/Dev3/PXI_Trig7',
                        MAX_name='Dev3',
                        connected_terminals=[('/Dev3/PXI_Trig0', '/Dev3/PXI_Trig7')])

In addition to clocking, the connected_terminals argument can be used to link output terminals on an NI DAQ module to shared triggers, then link those shared triggers to input terminals of another NI DAQ module in the same chassis.

AI timing skew

Given how the NI-DAQmx driver currently works, all of the outputs (and generally other hardware) are hardware-timed via direct outputs from the parent pseudoclocks. Under default usage, this is not true for the analog inputs of the DAQs, which are timed via the internal reference oscillator of the DAQ. Synchronization between the two is handled at the end by correlating start times and slicing the AI traces at the appropriate times. This works fine if the reference clocks for the pseudoclock and the DAQ don’t drift relative to each other, but that is generally not the case for a longer shot (on the order of 1 second) since the standard clocks for a pulseblaster and a DAQ both have accuracy on the order of 50 ppm.

With version 1.2.0 of the NI-DAQmx driver, this issue can be mitigated by suppling an external sample timebase that is phase synchronous with the DAQ’s pseudoclock device. This is done using the DAQmx SampleClkTimebase synchronization method. Simply provide an external clocking signal that is faster than the analog input sampling rate, and the DAQ will use an internall PLL to derive the AI sample clock from the provided timebase. Specifying an externally provided sample timebase is done using the AI_timebase_terminal and AI_timebase_rate arguments, which specify the input terminal (generally a PFI line) and the clock frequency.

Detailed Documentation

class labscript_devices.NI_DAQmx.labscript_devices.NI_DAQmx(name, parent_device=None, clock_terminal=None, MAX_name=None, static_AO=None, static_DO=None, clock_mirror_terminal=None, connected_terminals=None, acquisition_rate=None, AI_range=None, AI_range_Diff=None, AI_start_delay=0, AI_start_delay_ticks=None, AI_term='RSE', AI_term_cfg=None, AI_timebase_terminal=None, AI_timebase_rate=None, AO_range=None, max_AI_multi_chan_rate=None, max_AI_single_chan_rate=None, max_AO_sample_rate=None, max_DO_sample_rate=None, min_semiperiod_measurement=None, num_AI=0, num_AO=0, num_CI=0, ports=None, supports_buffered_AO=False, supports_buffered_DO=False, supports_semiperiod_measurement=False, supports_simultaneous_AI_sampling=False, **kwargs)[source]

Bases: IntermediateDevice

Generic class for NI_DAQmx devices.

Generally over-ridden by device-specific subclasses that contain the introspected default values.

Parameters:
  • name (str) – name to assign to the created labscript device

  • parent_device (clockline) – Parent clockline device that will clock the outputs of this device

  • clock_terminal (str) – What input on the DAQ is used for the clockline

  • MAX_name (str) – NI-MAX device name

  • static_AO (int, optional) – Number of static analog output channels.

  • static_DO (int, optional) – Number of static digital output channels.

  • clock_mirror_terminal (str, optional) – Channel string of digital output that mirrors the input clock. Useful for daisy-chaning DAQs on the same clockline.

  • connected_terminals (list, optional) – List of pairs of strings of digital inputs and digital outputs that will be connected. Useful for daisy-chaining DAQs on the same clockline when they do not have direct routes (see Device Routes in NI MAX).

  • acquisiton_rate (float, optional) – Default sample rate of inputs.

  • AI_range (iterable, optional) – A [Vmin, Vmax] pair that sets the analog input voltage range for all analog inputs.

  • AI_range_Diff (iterable, optional) – A [Vmin, Vmax] pair that sets the analog input voltage range for all analog inputs when using Differential termination.

  • AI_start_delay (float, optional) – Time in seconds between start of an analog input task starting and the first sample.

  • AI_start_delay_ticks (int, optional) – Time in sample clock periods between start of an analog input task starting and the first sample. To use this method, AI_start_delay must be set to None. This is necessary for DAQs that employ delta ADCs.

  • AI_term (str, optional) – Configures the analog input termination for all analog inputs. Must be supported by the device. Supported options are 'RSE', 'NRSE' 'Diff', and ‘PseudoDiff'.

  • AI_term_cfg (dict, optional) – Dictionary of analog input channels and their supported terminations. Best to use get_capabilities.py to introspect these.

  • AI_timebase_terminal (str, optional) – Channel string that specifies what channel to use for the Sample Clock Timebase signal. If None, use default internal clocks. Must also specify the rate when not using the internal sources.

  • AI_timebase_rate (float, optional) – Supplied clock frequency for the AI timebase. Only specify if using an external clock source.

  • AO_range (iterable, optional) – A [Vmin, Vmax] pair that sets the analog output voltage range for all analog outputs.

  • max_AI_multi_chan_rate (float, optional) – Max supported analog input sampling rate when using multiple channels.

  • max_AI_single_chan_rate (float, optional) – Max supported analog input sampling rate when only using a single channel.

  • max_AO_sample_rate (float, optional) – Max supported analog output sample rate.

  • max_DO_sample_rate (float, optional) – Max supported digital output sample rate.

  • min_sermiperiod_measurement (float, optional) – Minimum measurable time for a semiperiod measurement.

  • num_AI (int, optional) – Number of analog inputs channels.

  • num_AO (int, optional) – Number of analog output channels.

  • num_CI (int, optional) – Number of counter input channels.

  • ports (dict, optional) – Dictionarly of DIO ports, which number of lines and whether port supports buffered output.

  • supports_buffered_AO (bool, optional) – True if analog outputs support buffered output

  • supports_buffered_DO (bool, optional) – True if digital outputs support buffered output

  • supports_semiperiod_measurement (bool, optional) – True if device supports semi-period measurements

_check_AI_not_too_fast(AI_table)[source]

Check that analog input acquisition rates do not exceed maximums.

_check_bounds(analogs)[source]

Check that all analog outputs are in bounds

_check_even_children(analogs, digitals)[source]

Check that there are an even number of children of each type.

_check_wait_monitor_timeout_device_config()[source]

Check that if we are the wait monitor acquisition device and another device is the wait monitor timeout device, that a) the other device is a DAQmx device and b) the other device has a start_order lower than us and a stop_order higher than us.

_make_analog_input_table(inputs)[source]

Collect analog input instructions and create the acquisition table

_make_analog_out_table(analogs, times)[source]

Collect analog output data and create the output array

_make_digital_out_table(digitals, times)[source]

Collect digital output data and create the output array

add_device(device)[source]

Error checking for adding a child device.

Parameters:

device (labscript device) – Child labscript device to attach to this device. Only types of devices in allowed_children can be attached.

allowed_children = []

Sets the allowed children types based on the capabilites.

description = 'NI-DAQmx'

Brief description of the device.

generate_code(hdf5_file)[source]

Generates the hardware code from the script and saves it to the shot h5 file.

This is called automatically when a shot is compiled.

Parameters:

hdf5_file (str) – Path to shot’s hdf5 file to save the instructions to.

wait_monitor_supports_wait_completed_events = True
labscript_devices.NI_DAQmx.labscript_devices._smallest_int_type(n)[source]

Return the smallest unsigned integer type sufficient to contain n bits

class labscript_devices.NI_DAQmx.blacs_tabs.NI_DAQmxTab(notebook, settings, restart=False)[source]

Bases: DeviceTab

initialise_GUI()[source]
class labscript_devices.NI_DAQmx.blacs_workers.NI_DAQmxAcquisitionWorker(*args, **kwargs)[source]

Bases: Worker

MAX_READ_INTERVAL = 0.2
MAX_READ_PTS = 10000
abort_buffered()[source]
abort_transition_to_buffered()[source]
extract_measurements(raw_data, waits_in_use)[source]
init()[source]
program_manual(values)[source]
read(task_handle, event_type, num_samples, callback_data=None)[source]

Called as a callback by DAQmx while task is running. Also called by us to get remaining data just prior to stopping the task. Since the callback runs in a separate thread, we need to serialise access to instance variables

shutdown()[source]
start_task(chans, rate)[source]

Set up a task that acquires data with a callback every MAX_READ_PTS points or MAX_READ_INTERVAL seconds, whichever is faster. NI DAQmx calls callbacks in a separate thread, so this method returns, but data acquisition continues until stop_task() is called. Data is appended to self.acquired_data if self.buffered_mode=True, or (TODO) sent to the [whatever the AI server broker is called] if self.buffered_mode=False.

stop_task()[source]
transition_to_buffered(device_name, h5file, initial_values, fresh)[source]
transition_to_manual(abort=False)[source]
class labscript_devices.NI_DAQmx.blacs_workers.NI_DAQmxOutputWorker(*args, **kwargs)[source]

Bases: Worker

abort_buffered()[source]
abort_transition_to_buffered()[source]
check_version()[source]

Check the version of PyDAQmx is high enough to avoid a known bug

get_output_tables(h5file, device_name)[source]

Return the AO and DO tables rom the file, or None if they do not exist.

init()[source]
program_buffered_AO(AO_table)[source]
program_buffered_DO(DO_table)[source]

Create the DO task and program in the DO table for a shot. Return a dictionary of the final values of each channel in use

program_manual(front_panel_values)[source]
set_connected_terminals_connected(connected)[source]

Connect the terminals in the connected terminals list. Allows on daisy chaining of the clock line to/from other devices that do not have a direct route (see Device Routes in NI MAX).

set_mirror_clock_terminal_connected(connected)[source]

Mirror the clock terminal on another terminal to allow daisy chaining of the clock line to other devices, if applicable

shutdown()[source]
start_manual_mode_tasks()[source]
stop_tasks()[source]
transition_to_buffered(device_name, h5file, initial_values, fresh)[source]
transition_to_manual(abort=False)[source]
class labscript_devices.NI_DAQmx.blacs_workers.NI_DAQmxWaitMonitorWorker(*args, **kwargs)[source]

Bases: Worker

abort_buffered()[source]
abort_transition_to_buffered()[source]
init()[source]
program_manual(values)[source]
read_edges(npts, timeout=None)[source]

Wait up to the given timeout in seconds for an edge on the wait monitor and and return the duration since the previous edge. Return None upon timeout.

send_resume_trigger(pulse_width)[source]
shutdown()[source]
start_tasks()[source]
stop_tasks(abort)[source]
transition_to_buffered(device_name, h5file, initial_values, fresh)[source]
transition_to_manual(abort=False)[source]
wait_monitor()[source]
class labscript_devices.NI_DAQmx.runviewer_parsers.NI_DAQmxParser(path, device)[source]

Bases: object

get_traces(add_trace, clock=None)[source]
labscript_devices.NI_DAQmx.daqmx_utils.get_CI_chans(device_name)[source]
labscript_devices.NI_DAQmx.daqmx_utils.get_devices()[source]
labscript_devices.NI_DAQmx.daqmx_utils.get_product_type(device_name)[source]
labscript_devices.NI_DAQmx.daqmx_utils.incomplete_sample_detection(device_name)[source]

Introspect whether a device has ‘incomplete sample detection’, described here:

www.ni.com/documentation/en/ni-daqmx/latest/devconsid/incompletesampledetection/

The result is determined empirically by outputting a pulse on one counter and measuring it on another, and seeing whether the first sample was discarded or not. This requires a non-simulated device with at least two counters. No external signal is actually generated by the device whilst this test is performed. Credit for this method goes to Kevin Price, who provided it here:

forums.ni.com/t5/Multifunction-DAQ/_/td-p/3849429

This workaround will hopefully be deprecated if and when NI provides functionality to either inspect this feature’s presence directly, or to disable it regardless of its presence.

labscript_devices.NI_DAQmx.daqmx_utils.is_simulated(device_name)[source]
labscript_devices.NI_DAQmx.daqmx_utils.supports_period_measurement(device_name)[source]
labscript_devices.NI_DAQmx.utils.split_conn_AI(connection)[source]

Return analog input number of a connection string such as ‘ai1’ as an integer, or raise ValueError if format is invalid

labscript_devices.NI_DAQmx.utils.split_conn_AO(connection)[source]

Return analog output number of a connection string such as ‘ao1’ as an integer, or raise ValueError if format is invalid

labscript_devices.NI_DAQmx.utils.split_conn_DO(connection)[source]

Return the port and line number of a connection string such as ‘port0/line1 as two integers, or raise ValueError if format is invalid. Accepts connection strings such as port1/line0 (PFI0) - the PFI bit is just ignored

labscript_devices.NI_DAQmx.utils.split_conn_PFI(connection)[source]

Return PFI input number of a connection string such as ‘PFI0’ as an integer, or raise ValueError if format is invalid

labscript_devices.NI_DAQmx.utils.split_conn_port(connection)[source]

Return port number of a string such as ‘port0’ as an integer, or raise ValueError if format is invalid