Skip to content

Commit 1c756ed

Browse files
Merged in NI-DAQmx-wait-changes (pull request #76)
More flexible NI DAQmx wait monitor functionality.
2 parents 9f2c630 + f59a765 commit 1c756ed

File tree

5 files changed

+146
-35
lines changed

5 files changed

+146
-35
lines changed

NI_DAQmx/blacs_tabs.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
from blacs.device_base_class import DeviceTab
2424
from .utils import split_conn_AO, split_conn_DO
25+
from . import models
2526

2627

2728
class NI_DAQmxTab(DeviceTab):
@@ -155,21 +156,24 @@ def initialise_GUI(self):
155156
self.primary_worker = "main_worker"
156157

157158
if wait_acq_device == self.device_name:
158-
if wait_timeout_device != self.device_name:
159-
msg = """The wait monitor acquisition device must be the same as the
160-
wait timeout device."""
161-
raise RuntimeError(msg)
159+
if wait_timeout_device:
160+
wait_timeout_device = connection_table.find_by_name(wait_timeout_device)
161+
wait_timeout_MAX_name = wait_timeout_device.properties['MAX_name']
162+
else:
163+
wait_timeout_MAX_name = None
162164

163165
if num_CI == 0:
164-
msg = "Device cannot be a wait monitor as it has no counter inputs"
165-
raise RuntimeError(msg)
166+
msg = """Device cannot be the wait monitor acquisiiton device as it has
167+
no counter inputs"""
168+
raise RuntimeError(dedent(msg))
166169

167170
self.create_worker(
168171
"wait_monitor_worker",
169172
'labscript_devices.NI_DAQmx.blacs_workers.NI_DAQmxWaitMonitorWorker',
170173
{
171174
'MAX_name': self.MAX_name,
172175
'wait_acq_connection': wait_acq_connection,
176+
'wait_timeout_MAX_name': wait_timeout_MAX_name,
173177
'wait_timeout_connection': wait_timeout_connection,
174178
'timeout_trigger_type': timeout_trigger_type,
175179
'min_semiperiod_measurement': min_semiperiod_measurement,

NI_DAQmx/blacs_workers.py

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -759,13 +759,20 @@ def wait_monitor(self):
759759
semiperiods = self.read_edges(2, timeout)
760760
# Did the wait finish of its own accord, or time out?
761761
if semiperiods is None:
762-
# It timed out. Better trigger the clock to resume!
763-
msg = """Wait timed out; retriggering clock with {:.3e} s pulse
764-
({} edge)"""
765-
msg = dedent(msg).format(pulse_width, self.timeout_trigger_type)
766-
self.logger.debug(msg)
767-
self.send_resume_trigger(pulse_width)
768-
# Wait for it to respond to that:
762+
# It timed out. If there is a timeout device, send a trigger to
763+
# resume the clock!
764+
if self.DO_task is not None:
765+
msg = """Wait timed out; retriggering clock with {:.3e} s
766+
pulse ({} edge)"""
767+
msg = msg.format(pulse_width, self.timeout_trigger_type)
768+
self.logger.debug(dedent(msg))
769+
self.send_resume_trigger(pulse_width)
770+
else:
771+
msg = """Specified wait timeout exceeded, but there is no
772+
timeout device with which to resume the experiment.
773+
Continuing to wait."""
774+
self.logger.warning(dedent(msg))
775+
# Keep waiting for the clock to resume:
769776
self.logger.debug('Waiting for pulse indicating end of wait')
770777
semiperiods = self.read_edges(2, timeout=None)
771778
# Alright, now we're at the end of the wait.
@@ -815,7 +822,8 @@ def stop_tasks(self, abort):
815822
if not abort:
816823
# Don't want errors about incomplete task to be raised if we are aborting:
817824
self.CI_task.StopTask()
818-
self.DO_task.StopTask()
825+
if self.DO_task is not None:
826+
self.DO_task.StopTask()
819827
if self.CI_task is not None:
820828
self.CI_task.ClearTask()
821829
self.CI_task = None
@@ -849,15 +857,16 @@ def start_tasks(self):
849857
self.CI_task.StartTask()
850858

851859
# The timeout task:
852-
self.DO_task = Task()
853-
DO_chan = self.MAX_name + '/' + self.wait_timeout_connection
854-
self.DO_task.CreateDOChan(DO_chan, "", DAQmx_Val_ChanForAllLines)
855-
# Ensure timeout trigger is armed:
856-
written = int32()
857-
# Writing autostarts the task:
858-
self.DO_task.WriteDigitalLines(
859-
1, True, 1, DAQmx_Val_GroupByChannel, self.timeout_rearm, written, None
860-
)
860+
if self.wait_timeout_MAX_name is not None:
861+
self.DO_task = Task()
862+
DO_chan = self.wait_timeout_MAX_name + '/' + self.wait_timeout_connection
863+
self.DO_task.CreateDOChan(DO_chan, "", DAQmx_Val_ChanForAllLines)
864+
# Ensure timeout trigger is armed:
865+
written = int32()
866+
# Writing autostarts the task:
867+
self.DO_task.WriteDigitalLines(
868+
1, True, 1, DAQmx_Val_GroupByChannel, self.timeout_rearm, written, None
869+
)
861870

862871
def transition_to_buffered(self, device_name, h5file, initial_values, fresh):
863872
self.logger.debug('transition_to_buffered')

NI_DAQmx/labscript_devices.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
AnalogIn,
3232
bitfield,
3333
config,
34+
compiler,
3435
LabscriptError,
3536
set_passed_properties,
3637
)
@@ -377,6 +378,13 @@ def _make_analog_input_table(self, inputs):
377378
acq['units'],
378379
)
379380
)
381+
if acquisitions and compiler.wait_table and compiler.wait_monitor is None:
382+
msg = """Cannot do analog input on an NI DAQmx device in an experiment that
383+
uses waits without a wait monitor. This is because input data cannot be
384+
'chunked' into requested segments without knowledge of the durations of
385+
the waits. See labscript.WaitMonitor for details."""
386+
raise LabscriptError(dedent(msg))
387+
380388
# The 'a256' dtype below limits the string fields to 256
381389
# characters. Can't imagine this would be an issue, but to not
382390
# specify the string length (using dtype=str) causes the strings
@@ -396,6 +404,49 @@ def _make_analog_input_table(self, inputs):
396404

397405
return acquisition_table
398406

407+
def _check_wait_monitor_timeout_device_config(self):
408+
"""Check that if we are the wait monitor acquisition device and another device
409+
is the wait monitor timeout device, that a) the other device is a DAQmx device
410+
and b) the other device has a start_order lower than us and a stop_order higher
411+
than us."""
412+
acquisition_device = compiler.wait_monitor.acquisition_device
413+
timeout_device = compiler.wait_monitor.timeout_device
414+
if acquisition_device is not self or timeout_device is None:
415+
return
416+
if timeout_device is self:
417+
return
418+
if not isinstance(timeout_device, NI_DAQmx):
419+
msg = """If using an NI DAQmx device as a wait monitor acquisition device,
420+
then the wait monitor timeout device must also be an NI DAQmx device,
421+
not {}."""
422+
raise TypeError(dedent(msg).format(type(timeout_device)))
423+
timeout_start = timeout_device.start_order
424+
if timeout_start is None:
425+
timeout_start = 0
426+
timeout_stop = timeout_device.stop_order
427+
if timeout_stop is None:
428+
timeout_stop = 0
429+
self_start = self.start_order
430+
if self_start is None:
431+
self_start = 0
432+
self_stop = self.stop_order
433+
if self_stop is None:
434+
self_stop = 0
435+
if timeout_start >= self_start or timeout_stop <= self_stop:
436+
msg = """If using different DAQmx devices as the wait monitor acquisition
437+
and timeout devices, the timeout device must transition_to_buffered
438+
before the acquisition device, and transition_to_manual after it, in
439+
order to ensure the output port for timeout pulses is not in use (by the
440+
manual mode DO task) when the wait monitor subprocess attempts to use
441+
it. To achieve this, pass the start_order and stop_order keyword
442+
arguments to the devices in your connection table, ensuring that the
443+
timeout device has a lower start_order and a higher stop_order than the
444+
acquisition device. The default start_order and stop_order is zero, so
445+
if you are not otherwise controlling the order that devices are
446+
programmed, you can set start_order=-1, stop_order=1 on the timeout
447+
device only."""
448+
raise RuntimeError(dedent(msg))
449+
399450
def generate_code(self, hdf5_file):
400451
IntermediateDevice.generate_code(self, hdf5_file)
401452
analogs = {}
@@ -430,6 +481,7 @@ def generate_code(self, hdf5_file):
430481
AI_table = self._make_analog_input_table(inputs)
431482

432483
self._check_AI_not_too_fast(AI_table)
484+
self._check_wait_monitor_timeout_device_config()
433485

434486
grp = self.init_device_group(hdf5_file)
435487
if AO_table is not None:

PulseBlaster.py

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,23 @@
1616
str = unicode
1717

1818
from labscript_devices import BLACS_tab, runviewer_parser
19+
from labscript_utils import dedent
1920

20-
from labscript import Device, PseudoclockDevice, Pseudoclock, ClockLine, IntermediateDevice, DigitalQuantity, DigitalOut, DDS, DDSQuantity, config, LabscriptError, set_passed_properties
21+
from labscript import (
22+
Device,
23+
PseudoclockDevice,
24+
Pseudoclock,
25+
ClockLine,
26+
IntermediateDevice,
27+
DigitalQuantity,
28+
DigitalOut,
29+
DDS,
30+
DDSQuantity,
31+
config,
32+
LabscriptError,
33+
set_passed_properties,
34+
compiler,
35+
)
2136

2237
import numpy as np
2338

@@ -603,14 +618,30 @@ def write_pb_inst_to_h5(self, pb_inst, hdf5_file):
603618
group.create_dataset('PULSE_PROGRAM', compression=config.compression,data = pb_inst_table)
604619
self.set_property('stop_time', self.stop_time, location='device_properties')
605620

606-
621+
622+
def _check_wait_monitor_ok(self):
623+
if (
624+
compiler.master_pseudoclock is self
625+
and compiler.wait_table
626+
and compiler.wait_monitor is None
627+
and self.programming_scheme != 'pb_stop_programming/STOP'
628+
):
629+
msg = """If using waits without a wait monitor, the PulseBlaster used as a
630+
master pseudoclock must have
631+
programming_scheme='pb_stop_programming/STOP'. Otherwise there is no way
632+
for BLACS to distinguish between a wait, and the end of a shot. Either
633+
use a wait monitor (see labscript.WaitMonitor for details) or set
634+
programming_scheme='pb_stop_programming/STOP for %s."""
635+
raise LabscriptError(dedent(msg) % self.name)
636+
607637
def generate_code(self, hdf5_file):
608638
# Generate the hardware instructions
609-
hdf5_file.create_group('/devices/'+self.name)
639+
hdf5_file.create_group('/devices/' + self.name)
610640
PseudoclockDevice.generate_code(self, hdf5_file)
611641
dig_outputs, dds_outputs = self.get_direct_outputs()
612642
freqs, amps, phases = self.generate_registers(hdf5_file, dds_outputs)
613643
pb_inst = self.convert_to_pb_inst(dig_outputs, dds_outputs, freqs, amps, phases)
644+
self._check_wait_monitor_ok()
614645
self.write_pb_inst_to_h5(pb_inst, hdf5_file)
615646

616647

@@ -1042,10 +1073,17 @@ def transition_to_buffered(self,device_name,h5file,initial_values,fresh):
10421073
else:
10431074
raise ValueError('invalid programming_scheme %s'%str(self.programming_scheme))
10441075

1045-
# Are there waits in use in this experiment? The monitor waiting for the end of
1046-
# the experiment will need to know:
1047-
self.waits_pending = bool(len(hdf5_file['waits']))
1048-
1076+
# Are there waits in use in this experiment? The monitor waiting for the end
1077+
# of the experiment will need to know:
1078+
wait_monitor_exists = bool(hdf5_file['waits'].attrs['wait_monitor_acquisition_device'])
1079+
waits_in_use = bool(len(hdf5_file['waits']))
1080+
self.waits_pending = wait_monitor_exists and waits_in_use
1081+
if waits_in_use and not wait_monitor_exists:
1082+
# This should be caught during labscript compilation, but just in case.
1083+
# Having waits but not a wait monitor means we can't tell when the shot
1084+
# is over unless the shot ends in a STOP instruction:
1085+
assert self.programming_scheme == 'pb_stop_programming/STOP'
1086+
10491087
# Now we build a dictionary of the final state to send back to the GUI:
10501088
return_values = {'dds 0':{'freq':finalfreq0, 'amp':finalamp0, 'phase':finalphase0, 'gate':en0},
10511089
'dds 1':{'freq':finalfreq1, 'amp':finalamp1, 'phase':finalphase1, 'gate':en1},
@@ -1077,7 +1115,7 @@ def transition_to_manual(self):
10771115
if self.programming_scheme == 'pb_start/BRANCH':
10781116
done_condition = status['waiting']
10791117
elif self.programming_scheme == 'pb_stop_programming/STOP':
1080-
done_condition = True # status['stopped']
1118+
done_condition = status['stopped']
10811119

10821120
if time_based_shot_over is not None:
10831121
done_condition = time_based_shot_over

PulseBlaster_No_DDS.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ def generate_code(self, hdf5_file):
5454
PseudoclockDevice.generate_code(self, hdf5_file)
5555
dig_outputs, ignore = self.get_direct_outputs()
5656
pb_inst = self.convert_to_pb_inst(dig_outputs, [], {}, {}, {})
57+
self._check_wait_monitor_ok()
5758
self.write_pb_inst_to_h5(pb_inst, hdf5_file)
5859

5960

@@ -403,9 +404,16 @@ def transition_to_buffered(self,device_name,h5file,initial_values,fresh):
403404
else:
404405
raise ValueError('invalid programming_scheme %s'%str(self.programming_scheme))
405406

406-
# Are there waits in use in this experiment? The monitor waiting for the end of
407-
# the experiment will need to know:
408-
self.waits_pending = bool(len(hdf5_file['waits']))
407+
# Are there waits in use in this experiment? The monitor waiting for the end
408+
# of the experiment will need to know:
409+
wait_monitor_exists = bool(hdf5_file['waits'].attrs['wait_monitor_acquisition_device'])
410+
waits_in_use = bool(len(hdf5_file['waits']))
411+
self.waits_pending = wait_monitor_exists and waits_in_use
412+
if waits_in_use and not wait_monitor_exists:
413+
# This should be caught during labscript compilation, but just in case.
414+
# having waits but not a wait monitor means we can't tell when the shot
415+
# is over unless the shot ends in a STOP instruction:
416+
assert self.programming_scheme == 'pb_stop_programming/STOP'
409417

410418
# Now we build a dictionary of the final state to send back to the GUI:
411419
return_values = {}
@@ -436,7 +444,7 @@ def transition_to_manual(self):
436444
if self.programming_scheme == 'pb_start/BRANCH':
437445
done_condition = status['waiting']
438446
elif self.programming_scheme == 'pb_stop_programming/STOP':
439-
done_condition = True # status['stopped']
447+
done_condition = status['stopped']
440448

441449
if time_based_shot_over is not None:
442450
done_condition = time_based_shot_over

0 commit comments

Comments
 (0)