Skip to content

Commit 4d38383

Browse files
committed
Modifications to get_capabilities.py to handle DAQs to include details
about Differential input channels. New details include the AI range for diff channels (which may be higher than the single-ended ones) and the number of RSE and Diff physical channels. This can now handle DAQs that do not have any RSE inputs (like the simultaneous sampling ones). Plan to implement to option to use differential inputs next.
1 parent ef1e9b8 commit 4d38383

File tree

3 files changed

+318
-3
lines changed

3 files changed

+318
-3
lines changed
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#####################################################################
2+
# #
3+
# /NI_DAQmx/models/_subclass_template.py #
4+
# #
5+
# Copyright 2018, Christopher Billington #
6+
# #
7+
# This file is part of the module labscript_devices, in the #
8+
# labscript suite (see http://labscriptsuite.org), and is #
9+
# licensed under the Simplified BSD License. See the license.txt #
10+
# file in the root of the project for the full license. #
11+
# #
12+
#####################################################################
13+
14+
#####################################################################
15+
# WARNING #
16+
# #
17+
# This file is auto-generated, any modifications may be #
18+
# overwritten. See README.txt in this folder for details #
19+
# #
20+
#####################################################################
21+
22+
23+
from labscript_devices.NI_DAQmx.labscript_devices import NI_DAQmx
24+
25+
#:
26+
CAPABILITIES = {
27+
'AI_range': [-10.0, 10.0],
28+
'AI_range_Diff': [-10.0, 10.0],
29+
'AI_start_delay': None,
30+
'AI_start_delay_ticks': 64,
31+
'AI_term': 'PseudoDiff',
32+
'AI_term_cfg': {
33+
'ai0': ['PseudoDiff'],
34+
'ai1': ['PseudoDiff'],
35+
'ai10': ['PseudoDiff'],
36+
'ai11': ['PseudoDiff'],
37+
'ai12': ['PseudoDiff'],
38+
'ai13': ['PseudoDiff'],
39+
'ai14': ['PseudoDiff'],
40+
'ai15': ['PseudoDiff'],
41+
'ai2': ['PseudoDiff'],
42+
'ai3': ['PseudoDiff'],
43+
'ai4': ['PseudoDiff'],
44+
'ai5': ['PseudoDiff'],
45+
'ai6': ['PseudoDiff'],
46+
'ai7': ['PseudoDiff'],
47+
'ai8': ['PseudoDiff'],
48+
'ai9': ['PseudoDiff'],
49+
},
50+
'AO_range': None,
51+
'max_AI_multi_chan_rate': 204800.0,
52+
'max_AI_single_chan_rate': 204800.0,
53+
'max_AO_sample_rate': None,
54+
'max_DO_sample_rate': None,
55+
'min_semiperiod_measurement': None,
56+
'num_AI': 16,
57+
'num_AO': 0,
58+
'num_CI': 0,
59+
'ports': {},
60+
'supports_buffered_AO': False,
61+
'supports_buffered_DO': False,
62+
'supports_semiperiod_measurement': False,
63+
'supports_simultaneous_AI_sampling': True,
64+
}
65+
66+
67+
class NI_PXIe_4499(NI_DAQmx):
68+
description = 'NI-PXIe-4499'
69+
70+
def __init__(self, *args, **kwargs):
71+
"""Class for NI-PXIe-4499"""
72+
# Any provided kwargs take precedent over capabilities
73+
combined_kwargs = CAPABILITIES.copy()
74+
combined_kwargs.update(kwargs)
75+
NI_DAQmx.__init__(self, *args, **combined_kwargs)
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
#####################################################################
2+
# #
3+
# /NI_DAQmx/models/_subclass_template.py #
4+
# #
5+
# Copyright 2018, Christopher Billington #
6+
# #
7+
# This file is part of the module labscript_devices, in the #
8+
# labscript suite (see http://labscriptsuite.org), and is #
9+
# licensed under the Simplified BSD License. See the license.txt #
10+
# file in the root of the project for the full license. #
11+
# #
12+
#####################################################################
13+
14+
#####################################################################
15+
# WARNING #
16+
# #
17+
# This file is auto-generated, any modifications may be #
18+
# overwritten. See README.txt in this folder for details #
19+
# #
20+
#####################################################################
21+
22+
23+
from labscript_devices.NI_DAQmx.labscript_devices import NI_DAQmx
24+
25+
#:
26+
CAPABILITIES = {
27+
'AI_range': [-10.0, 10.0],
28+
'AI_range_Diff': [-10.0, 10.0],
29+
'AI_start_delay': 7e-08,
30+
'AI_term': 'RSE',
31+
'AI_term_cfg': {
32+
'ai0': ['RSE', 'NRSE', 'Diff'],
33+
'ai1': ['RSE', 'NRSE', 'Diff'],
34+
'ai10': ['RSE', 'NRSE'],
35+
'ai11': ['RSE', 'NRSE'],
36+
'ai12': ['RSE', 'NRSE'],
37+
'ai13': ['RSE', 'NRSE'],
38+
'ai14': ['RSE', 'NRSE'],
39+
'ai15': ['RSE', 'NRSE'],
40+
'ai16': ['RSE', 'NRSE', 'Diff'],
41+
'ai17': ['RSE', 'NRSE', 'Diff'],
42+
'ai18': ['RSE', 'NRSE', 'Diff'],
43+
'ai19': ['RSE', 'NRSE', 'Diff'],
44+
'ai2': ['RSE', 'NRSE', 'Diff'],
45+
'ai20': ['RSE', 'NRSE', 'Diff'],
46+
'ai21': ['RSE', 'NRSE', 'Diff'],
47+
'ai22': ['RSE', 'NRSE', 'Diff'],
48+
'ai23': ['RSE', 'NRSE', 'Diff'],
49+
'ai24': ['RSE', 'NRSE'],
50+
'ai25': ['RSE', 'NRSE'],
51+
'ai26': ['RSE', 'NRSE'],
52+
'ai27': ['RSE', 'NRSE'],
53+
'ai28': ['RSE', 'NRSE'],
54+
'ai29': ['RSE', 'NRSE'],
55+
'ai3': ['RSE', 'NRSE', 'Diff'],
56+
'ai30': ['RSE', 'NRSE'],
57+
'ai31': ['RSE', 'NRSE'],
58+
'ai4': ['RSE', 'NRSE', 'Diff'],
59+
'ai5': ['RSE', 'NRSE', 'Diff'],
60+
'ai6': ['RSE', 'NRSE', 'Diff'],
61+
'ai7': ['RSE', 'NRSE', 'Diff'],
62+
'ai8': ['RSE', 'NRSE'],
63+
'ai9': ['RSE', 'NRSE'],
64+
},
65+
'AO_range': [-10.0, 10.0],
66+
'max_AI_multi_chan_rate': 1000000.0,
67+
'max_AI_single_chan_rate': 2000000.0,
68+
'max_AO_sample_rate': 2857142.8571428573,
69+
'max_DO_sample_rate': 10000000.0,
70+
'min_semiperiod_measurement': 1e-07,
71+
'num_AI': 32,
72+
'num_AO': 4,
73+
'num_CI': 4,
74+
'ports': {
75+
'port0': {'num_lines': 32, 'supports_buffered': True},
76+
'port1': {'num_lines': 8, 'supports_buffered': False},
77+
'port2': {'num_lines': 8, 'supports_buffered': False},
78+
},
79+
'supports_buffered_AO': True,
80+
'supports_buffered_DO': True,
81+
'supports_semiperiod_measurement': True,
82+
'supports_simultaneous_AI_sampling': False,
83+
}
84+
85+
86+
class NI_USB_6363(NI_DAQmx):
87+
description = 'NI-USB-6363'
88+
89+
def __init__(self, *args, **kwargs):
90+
"""Class for NI-USB-6363"""
91+
# Any provided kwargs take precedent over capabilities
92+
combined_kwargs = CAPABILITIES.copy()
93+
combined_kwargs.update(kwargs)
94+
NI_DAQmx.__init__(self, *args, **combined_kwargs)

labscript_devices/NI_DAQmx/models/get_capabilities.py

Lines changed: 149 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ def wrapped2(name):
178178
DAQmxGetDevAIMaxMultiChanRate = float64_prop(PyDAQmx.DAQmxGetDevAIMaxMultiChanRate)
179179
DAQmxGetDevAOVoltageRngs = float64_array_prop(PyDAQmx.DAQmxGetDevAOVoltageRngs)
180180
DAQmxGetDevAIVoltageRngs = float64_array_prop(PyDAQmx.DAQmxGetDevAIVoltageRngs)
181+
DAQmxGetPhysicalChanAITermCfgs = int32_prop(PyDAQmx.DAQmxGetPhysicalChanAITermCfgs)
181182

182183

183184
def port_supports_buffered(device_name, port, clock_terminal=None):
@@ -252,8 +253,13 @@ def AI_start_delay(device_name):
252253
Vmin, Vmax = DAQmxGetDevAIVoltageRngs(device_name)[0:2]
253254
num_samples = 1000
254255
chan = device_name + '/ai0'
256+
supp_types = DAQmxGetPhysicalChanAITermCfgs(chan)
257+
if supp_types & c.DAQmx_Val_Bit_TermCfg_RSE:
258+
input_type = c.DAQmx_Val_RSE
259+
elif supp_types & c.DAQmx_Val_Bit_TermCfg_Diff:
260+
input_type = c.DAQmx_Val_Diff
255261
task.CreateAIVoltageChan(
256-
chan, "", c.DAQmx_Val_RSE, Vmin, Vmax, c.DAQmx_Val_Volts, None
262+
chan, "", input_type, Vmin, Vmax, c.DAQmx_Val_Volts, None
257263
)
258264
task.CfgSampClkTiming(
259265
"", rate, c.DAQmx_Val_Rising, c.DAQmx_Val_ContSamps, num_samples
@@ -265,7 +271,12 @@ def AI_start_delay(device_name):
265271
sample_timebase_rate = float64()
266272

267273
task.GetStartTrigDelay(start_trig_delay)
268-
task.GetDelayFromSampClkDelay(delay_from_sample_clock)
274+
try:
275+
task.GetDelayFromSampClkDelay(delay_from_sample_clock)
276+
except PyDAQmx.DAQmxFunctions.AttributeNotSupportedInTaskContextError:
277+
# seems simultaneous sampling devices do not have this property,
278+
# so assume it is zero
279+
delay_from_sample_clock.value = 0
269280
task.GetSampClkTimebaseRate(sample_timebase_rate)
270281

271282
task.ClearTask()
@@ -275,6 +286,26 @@ def AI_start_delay(device_name):
275286
return total_delay_in_seconds
276287

277288

289+
def supported_AI_terminal_configurations(device_name):
290+
"""Determine which analong input configurations are supported for each AI.
291+
292+
Valid options are RSE, NRSE, Diff, and PseudoDiff.
293+
The labscript driver only supports RSE, NRSE, and Diff.
294+
"""
295+
supp_types = {}
296+
poss_types = {'RSE': c.DAQmx_Val_Bit_TermCfg_RSE,
297+
'NRSE': c.DAQmx_Val_Bit_TermCfg_NRSE,
298+
'Diff': c.DAQmx_Val_Bit_TermCfg_Diff,
299+
'PseudoDiff': c.DAQmx_Val_Bit_TermCfg_PseudoDIFF}
300+
chans = DAQmxGetDevAIPhysicalChans(device_name)
301+
for chan in chans:
302+
byte = DAQmxGetPhysicalChanAITermCfgs(device_name+'/'+chan)
303+
chan_types = [key for key, val in poss_types.items() if val & byte]
304+
supp_types[chan] = chan_types
305+
306+
return supp_types
307+
308+
278309
def supported_AI_ranges_for_non_differential_input(device_name, AI_ranges):
279310
"""Empirically determine the analog input voltage ranges for non-differential inputs.
280311
@@ -403,7 +434,8 @@ def get_min_semiperiod_measurement(device_name):
403434

404435
models = []
405436
for name in DAQmxGetSysDevNames().split(', '):
406-
model = DAQmxGetDevProductType(name)
437+
# ignore extra details in model names
438+
model = DAQmxGetDevProductType(name).split(' ')[0]
407439
print("found device:", name, model)
408440
if model not in models:
409441
models.append(model)
@@ -495,6 +527,120 @@ def get_min_semiperiod_measurement(device_name):
495527
capabilities[model]["AI_range"] = [Vmin, Vmax]
496528
else:
497529
capabilities[model]["AI_range"] = None
530+
capabilities = json.load(f)
531+
except ValueError:
532+
pass
533+
534+
535+
models = []
536+
for name in DAQmxGetSysDevNames().split(', '):
537+
# ignore extra details in model names
538+
model = DAQmxGetDevProductType(name).split(' ')[0]
539+
print("found device:", name, model)
540+
if model not in models:
541+
models.append(model)
542+
capabilities[model] = {}
543+
try:
544+
capabilities[model]["supports_buffered_AO"] = DAQmxGetDevAOSampClkSupported(
545+
name
546+
)
547+
except PyDAQmx.DAQmxFunctions.AttrNotSupportedError:
548+
capabilities[model]["supports_buffered_AO"] = False
549+
try:
550+
capabilities[model]["max_DO_sample_rate"] = DAQmxGetDevDOMaxRate(name)
551+
capabilities[model]["supports_buffered_DO"] = True
552+
except PyDAQmx.DAQmxFunctions.AttrNotSupportedError:
553+
capabilities[model]["max_DO_sample_rate"] = None
554+
capabilities[model]["supports_buffered_DO"] = False
555+
if capabilities[model]["supports_buffered_AO"]:
556+
capabilities[model]["max_AO_sample_rate"] = DAQmxGetDevAOMaxRate(name)
557+
else:
558+
capabilities[model]["max_AO_sample_rate"] = None
559+
560+
capabilities[model]["num_AO"] = len(DAQmxGetDevAOPhysicalChans(name))
561+
capabilities[model]["num_AI"] = len(DAQmxGetDevAIPhysicalChans(name))
562+
if capabilities[model]["num_AI"] > 0:
563+
single_rate = DAQmxGetDevAIMaxSingleChanRate(name)
564+
multi_rate = DAQmxGetDevAIMaxMultiChanRate(name)
565+
else:
566+
single_rate = None
567+
multi_rate = None
568+
capabilities[model]["max_AI_single_chan_rate"] = single_rate
569+
capabilities[model]["max_AI_multi_chan_rate"] = multi_rate
570+
if capabilities[model]["num_AI"] > 0:
571+
capabilities[model]["AI_term_cfg"] = supported_AI_terminal_configurations(name)
572+
cfgs = [item for sublist in capabilities[model]["AI_term_cfg"].values()
573+
for item in sublist]
574+
capabilities[model]["num_AI_Diff"] = cfgs.count('Diff')
575+
capabilities[model]["num_AI_RSE"] = cfgs.count('RSE')
576+
else:
577+
capabilities[model]["AI_term_cfg"] = None
578+
579+
capabilities[model]["ports"] = {}
580+
ports = DAQmxGetDevDOPorts(name)
581+
chans = DAQmxGetDevDOLines(name)
582+
for port in ports:
583+
if '_' in port:
584+
# Ignore the alternate port names such as 'port0_32' that allow using two or
585+
# more ports together as a single, larger one:
586+
continue
587+
port_info = {}
588+
capabilities[model]["ports"][port] = port_info
589+
port_chans = [chan for chan in chans if chan.split('/')[0] == port]
590+
port_info['num_lines'] = len(port_chans)
591+
if capabilities[model]["supports_buffered_DO"]:
592+
port_info['supports_buffered'] = port_supports_buffered(name, port)
593+
else:
594+
port_info['supports_buffered'] = False
595+
596+
capabilities[model]["num_CI"] = len(DAQmxGetDevCIPhysicalChans(name))
597+
supports_semiperiod = supports_semiperiod_measurement(name)
598+
capabilities[model]["supports_semiperiod_measurement"] = supports_semiperiod
599+
if capabilities[model]["num_CI"] > 0 and supports_semiperiod:
600+
min_semiperiod_measurement = get_min_semiperiod_measurement(name)
601+
else:
602+
min_semiperiod_measurement = None
603+
capabilities[model]["min_semiperiod_measurement"] = min_semiperiod_measurement
604+
605+
if capabilities[model]['num_AO'] > 0:
606+
AO_ranges = []
607+
raw_limits = DAQmxGetDevAOVoltageRngs(name)
608+
for i in range(0, len(raw_limits), 2):
609+
Vmin, Vmax = raw_limits[i], raw_limits[i + 1]
610+
AO_ranges.append([Vmin, Vmax])
611+
# Find range with the largest maximum voltage and use that:
612+
Vmin, Vmax = max(AO_ranges, key=lambda range: range[1])
613+
# Confirm that no other range has a voltage lower than Vmin,
614+
# since if it does, this violates our assumptions and things might not
615+
# be as simple as having a single range:
616+
assert min(AO_ranges)[0] >= Vmin
617+
capabilities[model]["AO_range"] = [Vmin, Vmax]
618+
else:
619+
capabilities[model]["AO_range"] = None
620+
621+
if capabilities[model]['num_AI'] > 0:
622+
AI_ranges = []
623+
raw_limits = DAQmxGetDevAIVoltageRngs(name)
624+
for i in range(0, len(raw_limits), 2):
625+
Vmin, Vmax = raw_limits[i], raw_limits[i + 1]
626+
AI_ranges.append([Vmin, Vmax])
627+
# Find range with the largest maximum voltage and use that:
628+
Vmin_raw, Vmax_raw = max(AI_ranges, key=lambda range: range[1])
629+
# Confirm that no other range has a voltage lower than Vmin,
630+
# since if it does, this violates our assumptions and things might not
631+
# be as simple as having a single range:
632+
assert min(AI_ranges)[0] >= Vmin_raw
633+
capabilities[model]["AI_range_Diff"] = [Vmin_raw, Vmax_raw]
634+
if 'RSE' in capabilities[model]["AI_term_cfg"]['ai0']:
635+
# Now limit to non-differential inputs (if available), which may have lower ranges
636+
AI_ranges = supported_AI_ranges_for_non_differential_input(name, AI_ranges)
637+
# Find RSE range with the largest maximum voltage and use that:
638+
Vmin, Vmax = max(AI_ranges, key=lambda range: range[1])
639+
assert min(AI_ranges)[0] >= Vmin
640+
capabilities[model]["AI_range"] = [Vmin, Vmax]
641+
else:
642+
capabilities[model]["AI_range"] = None
643+
capabilities[model]["AI_range_Diff"] = None
498644

499645
if capabilities[model]["num_AI"] > 0:
500646
capabilities[model]["AI_start_delay"] = AI_start_delay(name)

0 commit comments

Comments
 (0)